@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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TETO_DA_MENSAGEM = exports.CONSEQUENCIA_DA_ACAO = exports.LETRAS = exports.TETO_DE_PERGUNTAS_POR_LOTE = exports.CONTRATO_LOTE = void 0;
|
|
4
|
+
exports.alternativaRecomendada = alternativaRecomendada;
|
|
5
|
+
exports.perguntaDoPedido = perguntaDoPedido;
|
|
6
|
+
exports.montarLote = montarLote;
|
|
7
|
+
exports.guardadasDepoisDoLote = guardadasDepoisDoLote;
|
|
8
|
+
exports.linhaDasRecusas = linhaDasRecusas;
|
|
9
|
+
exports.textoDoLote = textoDoLote;
|
|
10
|
+
/**
|
|
11
|
+
* I-41 (D4 e D16), CAMADA 2: o lote que so sai depois do sim.
|
|
12
|
+
*
|
|
13
|
+
* O dono desenhou assim: "so depois do SIM. Ai sim chegam as perguntas, topificadas, DE 5 EM 5,
|
|
14
|
+
* cada uma com alternativas a, b, c, d e UMA recomendada com o porque em uma linha, e a
|
|
15
|
+
* consequencia de cada alternativa em uma linha."
|
|
16
|
+
*
|
|
17
|
+
* O que muda em relacao ao lote que o PLAN ja previa (D4) e uma coisa so: ele nao sai sozinho.
|
|
18
|
+
* `loteLiberado` do consentimento e a porta.
|
|
19
|
+
*
|
|
20
|
+
* A REGRA QUE FECHA O BURACO DE E5. Um pedido so entra no lote quando da para dizer, sem
|
|
21
|
+
* adivinhar, qual alternativa e a recomendada. Hoje o produto emite "Confira artefatos, claims e
|
|
22
|
+
* riscos antes de responder.", que nao aponta alternativa nenhuma; pedido assim e RECUSADO com
|
|
23
|
+
* motivo `hitl.formato` e contado a parte, em vez de sair com uma recomendada escolhida no chute
|
|
24
|
+
* pelo agente. Recusa de formato e defeito do EMISSOR, corrigivel por spec dirigida, e nunca
|
|
25
|
+
* escalacao para o dono: escalar formato para o humano seria pedir que ele revise a sintaxe do
|
|
26
|
+
* robo. O item continua na fila e continua contado no resumo; ele so nao vira pergunta.
|
|
27
|
+
*
|
|
28
|
+
* A consequencia de cada alternativa NAO e inventada: ela sai da acao que a alternativa executa,
|
|
29
|
+
* por mapa congelado. Acao e o que de fato acontece; texto livre seria opiniao.
|
|
30
|
+
*/
|
|
31
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
32
|
+
const hitl_1 = require("./hitl");
|
|
33
|
+
exports.CONTRATO_LOTE = 'ork.hitl-lote/v1';
|
|
34
|
+
/** De cinco em cinco, como o dono pediu. As que sobram continuam guardadas, nao somem. */
|
|
35
|
+
exports.TETO_DE_PERGUNTAS_POR_LOTE = 5;
|
|
36
|
+
/** De 2 a 4 alternativas, rotuladas em sequencia. Cinco nao cabem numa escolha rapida. */
|
|
37
|
+
exports.LETRAS = ['a', 'b', 'c', 'd'];
|
|
38
|
+
/**
|
|
39
|
+
* O que acontece ao escolher, por acao. Mapa TOTAL sobre as acoes do pedido: acao nova quebra o
|
|
40
|
+
* build aqui, que e o comportamento desejado. Uma alternativa sem consequencia dita e uma
|
|
41
|
+
* escolha feita no escuro.
|
|
42
|
+
*/
|
|
43
|
+
exports.CONSEQUENCIA_DA_ACAO = Object.freeze({
|
|
44
|
+
aprovar: 'a fase segue e o bloco avança',
|
|
45
|
+
recusar: 'a fase volta para revisão',
|
|
46
|
+
responder: 'a resposta é encaminhada à sessão que perguntou',
|
|
47
|
+
esperar: 'nada muda; o item continua na fila',
|
|
48
|
+
});
|
|
49
|
+
/** Teto de uma linha. Consequência que vira parágrafo deixa de ser leitura rápida. */
|
|
50
|
+
const LIMITE_DE_LINHA = 140;
|
|
51
|
+
const linha = (texto) => (0, hitl_1.redigirSegredos)(String(texto)).replace(/\s+/g, ' ').trim().slice(0, LIMITE_DE_LINHA);
|
|
52
|
+
/** As alternativas de um pedido v2, quando ele ja traz as suas prontas. */
|
|
53
|
+
function alternativasDeclaradas(pedido) {
|
|
54
|
+
const brutas = pedido.alternativas;
|
|
55
|
+
if (!Array.isArray(brutas) || brutas.length < 2 || brutas.length > exports.LETRAS.length)
|
|
56
|
+
return undefined;
|
|
57
|
+
const alternativas = [];
|
|
58
|
+
let recomendadas = 0;
|
|
59
|
+
for (const [i, bruta] of brutas.entries()) {
|
|
60
|
+
const o = bruta;
|
|
61
|
+
if (typeof o.texto !== 'string' || typeof o.consequencia !== 'string')
|
|
62
|
+
return undefined;
|
|
63
|
+
const recomendada = o.recomendada === true;
|
|
64
|
+
if (recomendada) {
|
|
65
|
+
if (typeof o.porque !== 'string' || !o.porque.trim())
|
|
66
|
+
return undefined;
|
|
67
|
+
recomendadas++;
|
|
68
|
+
}
|
|
69
|
+
alternativas.push({
|
|
70
|
+
letra: exports.LETRAS[i], texto: linha(o.texto), consequencia: linha(o.consequencia),
|
|
71
|
+
...(recomendada ? { recomendada: { porque: linha(String(o.porque)) } } : {}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return recomendadas === 1 ? alternativas : undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Qual alternativa a recomendacao do pedido NOMEIA, ou -1 quando ela nao nomeia nenhuma.
|
|
78
|
+
*
|
|
79
|
+
* A regra e literal de proposito: a recomendacao precisa conter o texto de uma das opcoes. Uma
|
|
80
|
+
* recomendacao que cita duas e ambigua e tambem nao serve. Nada aqui interpreta intencao.
|
|
81
|
+
*/
|
|
82
|
+
function alternativaRecomendada(pedido) {
|
|
83
|
+
const recomendacao = (0, hitl_contract_1.recomendacaoDoPedido)(pedido).toLowerCase();
|
|
84
|
+
const citadas = (0, hitl_contract_1.escolhasDoPedido)(pedido)
|
|
85
|
+
.map((o, i) => ({ i, achou: !!o.texto.trim() && recomendacao.includes(o.texto.trim().toLowerCase()) }))
|
|
86
|
+
.filter(o => o.achou);
|
|
87
|
+
return citadas.length === 1 ? citadas[0].i : -1;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Traduz um pedido em pergunta do lote, ou diz por que ele nao virou uma.
|
|
91
|
+
*
|
|
92
|
+
* Duas fontes de alternativa, nesta ordem: as declaradas pelo proprio pedido (`ork.hitl/v2`) e,
|
|
93
|
+
* na falta delas, as opcoes de um pedido `ork.hitl/v1` com a consequencia vinda do mapa
|
|
94
|
+
* congelado e a recomendada sendo a que a recomendacao do pedido NOMEIA.
|
|
95
|
+
*/
|
|
96
|
+
function perguntaDoPedido(pedido, numero) {
|
|
97
|
+
const recusa = (detalhe) => ({ pedidoId: pedido.id, thread: pedido.thread ?? null, motivo: 'hitl.formato', detalhe });
|
|
98
|
+
// I-41 (T4c): um fato consumado nao e pergunta. Ele nao tem alvo, nao tem escolha e nao pede
|
|
99
|
+
// nada; deixar `textoDoPedido` devolver o que foi decidido faria a camada 2 perguntar sobre
|
|
100
|
+
// algo que ja aconteceu. A recusa e por ausencia de alvo, que e o criterio estrutural.
|
|
101
|
+
if (!(0, hitl_contract_1.alvoDoPedido)(pedido))
|
|
102
|
+
return recusa('decisão informada não é pergunta');
|
|
103
|
+
const pergunta = linha((0, hitl_contract_1.textoDoPedido)(pedido));
|
|
104
|
+
if (!pergunta)
|
|
105
|
+
return recusa('pergunta vazia');
|
|
106
|
+
const declaradas = alternativasDeclaradas(pedido);
|
|
107
|
+
if (declaradas) {
|
|
108
|
+
return { numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, alternativas: declaradas, pedidoId: pedido.id };
|
|
109
|
+
}
|
|
110
|
+
// I-41 (T4c): o parser da recomendacao e exclusivo do v1, e a porta se fecha aqui.
|
|
111
|
+
//
|
|
112
|
+
// Um v2 com duas recomendadas, ou sem consequencia, nao produz alternativas declaradas. Sem
|
|
113
|
+
// esta linha ele cairia no caminho de baixo, onde `recomendacaoDoPedido` devolve o texto da
|
|
114
|
+
// PRIMEIRA recomendada; esse texto cita o rotulo de uma opcao, o parser acha exatamente uma e
|
|
115
|
+
// o pedido malformado entraria no lote com uma recomendada escolhida por acidente. E
|
|
116
|
+
// exatamente o chute que a camada 2 existe para recusar, so que vindo por dentro.
|
|
117
|
+
if ((0, hitl_contract_1.ehV2)(pedido))
|
|
118
|
+
return recusa('alternativas declaradas inválidas: v2 não cai no parser do v1');
|
|
119
|
+
const opcoes = (0, hitl_contract_1.escolhasDoPedido)(pedido);
|
|
120
|
+
if (opcoes.length < 2)
|
|
121
|
+
return recusa('menos de 2 alternativas');
|
|
122
|
+
if (opcoes.length > exports.LETRAS.length)
|
|
123
|
+
return recusa(`mais de ${exports.LETRAS.length} alternativas`);
|
|
124
|
+
const recomendada = alternativaRecomendada(pedido);
|
|
125
|
+
if (recomendada < 0)
|
|
126
|
+
return recusa('a recomendação não nomeia exatamente uma alternativa');
|
|
127
|
+
return {
|
|
128
|
+
numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, pedidoId: pedido.id,
|
|
129
|
+
alternativas: opcoes.map((o, i) => ({
|
|
130
|
+
letra: exports.LETRAS[i], texto: linha(o.texto), consequencia: exports.CONSEQUENCIA_DA_ACAO[o.acao],
|
|
131
|
+
...(i === recomendada ? { recomendada: { porque: linha((0, hitl_contract_1.recomendacaoDoPedido)(pedido)) } } : {}),
|
|
132
|
+
})),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* O lote de ate cinco perguntas. O que sobra continua guardado e vai no proximo, e o que foi
|
|
137
|
+
* recusado por formato e contado a parte, para que ninguem confunda "nao perguntei" com
|
|
138
|
+
* "perguntei e nao houve resposta".
|
|
139
|
+
*/
|
|
140
|
+
function montarLote(pedidos, opcoes = {}) {
|
|
141
|
+
const teto = opcoes.teto ?? exports.TETO_DE_PERGUNTAS_POR_LOTE;
|
|
142
|
+
// O numero continua de um lote para o outro enquanto o anterior ainda pode ser respondido:
|
|
143
|
+
// "1a" nunca pode querer dizer duas perguntas diferentes ao mesmo tempo.
|
|
144
|
+
const inicio = opcoes.numeroInicial ?? 1;
|
|
145
|
+
const perguntas = [], recusadas = [];
|
|
146
|
+
const vistos = new Set();
|
|
147
|
+
let elegiveis = 0, abertas = 0;
|
|
148
|
+
for (const pedido of pedidos) {
|
|
149
|
+
// O mesmo pedido chega por dois itens do pulse quando a sessao e a fase param pelo mesmo gate.
|
|
150
|
+
// Perguntar duas vezes a mesma coisa, com dois numeros, era um jeito de o dono responder errado.
|
|
151
|
+
if (vistos.has(pedido.id))
|
|
152
|
+
continue;
|
|
153
|
+
vistos.add(pedido.id);
|
|
154
|
+
// A3: `tipoDeResposta` e lido. Aberta nao se responde por letra e nao sai no meio das
|
|
155
|
+
// objetivas: fica fora deste lote e e contada, para ninguem confundir fora com perdida.
|
|
156
|
+
if ((0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' && pedido.tipoDeResposta === 'aberta') {
|
|
157
|
+
abertas++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const resultado = perguntaDoPedido(pedido, inicio + perguntas.length);
|
|
161
|
+
if ('motivo' in resultado) {
|
|
162
|
+
recusadas.push(resultado);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
elegiveis++;
|
|
166
|
+
if (perguntas.length < teto)
|
|
167
|
+
perguntas.push(resultado);
|
|
168
|
+
}
|
|
169
|
+
return { contrato: exports.CONTRATO_LOTE, perguntas, restantes: elegiveis - perguntas.length, recusadas, abertas };
|
|
170
|
+
}
|
|
171
|
+
/** Quantas perguntas continuam guardadas depois deste lote, para o resumo citar. */
|
|
172
|
+
function guardadasDepoisDoLote(lote) {
|
|
173
|
+
return lote.restantes;
|
|
174
|
+
}
|
|
175
|
+
function corpoDaPergunta(p, marca) {
|
|
176
|
+
const cabecalho = [p.thread, p.fase].filter(Boolean).join(' · ');
|
|
177
|
+
return [
|
|
178
|
+
`${p.numero}. ${cabecalho || 'sem thread'}`,
|
|
179
|
+
` ${p.pergunta}`,
|
|
180
|
+
...p.alternativas.flatMap(a => [
|
|
181
|
+
` ${a.letra}) ${a.texto}${a.recomendada ? ` ${marca}recomendada` : ''}`,
|
|
182
|
+
` → ${a.consequencia}`,
|
|
183
|
+
...(a.recomendada ? [` porquê: ${a.recomendada.porque}`] : []),
|
|
184
|
+
]),
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
/** A5: a frase dos pedidos que nao viraram pergunta, com a concordancia certa para o dono ler. */
|
|
188
|
+
function linhaDasRecusas(n) {
|
|
189
|
+
return n === 1
|
|
190
|
+
? '1 pedido não virou pergunta por defeito de quem escreveu; o conserto é nosso, não seu.'
|
|
191
|
+
: `${n} pedidos não viraram pergunta por defeito de quem escreveu; o conserto é nosso, não seu.`;
|
|
192
|
+
}
|
|
193
|
+
/** Teto de uma mensagem de Telegram, com folga: o mesmo que a mensagem por item ja usava. */
|
|
194
|
+
exports.TETO_DA_MENSAGEM = 3900;
|
|
195
|
+
/**
|
|
196
|
+
* O texto do lote no canal. Mesma regra da camada 1: quem monta e o nucleo, e o humano nunca
|
|
197
|
+
* recebe JSON nem identificador longo. Ele responde pelo numero da pergunta mais a letra.
|
|
198
|
+
*/
|
|
199
|
+
function textoDoLote(lote, opcoes) {
|
|
200
|
+
if (lote.contrato !== exports.CONTRATO_LOTE)
|
|
201
|
+
throw new Error('lote HITL: contrato inválido');
|
|
202
|
+
const telegram = opcoes.canal === 'telegram';
|
|
203
|
+
const n = lote.perguntas.length;
|
|
204
|
+
const exemplo = lote.perguntas.map(p => `${p.numero}${p.alternativas[0].letra}`).join(' ');
|
|
205
|
+
const faltam = opcoes.proximo?.faltam ?? lote.restantes;
|
|
206
|
+
return [
|
|
207
|
+
`${telegram ? '📋 ' : ''}Orkastery, ${n === 1 ? '1 pergunta' : `${n} perguntas`}`,
|
|
208
|
+
'',
|
|
209
|
+
...lote.perguntas.flatMap((p, i) => [...corpoDaPergunta(p, telegram ? '✅ ' : '← '), ...(i < n - 1 ? [''] : [])]),
|
|
210
|
+
'',
|
|
211
|
+
...(n ? [`Responda com o número e a letra, por exemplo: ${exemplo}.`] : ['Nenhuma pergunta em formato respondível agora.']),
|
|
212
|
+
...(opcoes.proximo && faltam
|
|
213
|
+
? [`Falta${faltam === 1 ? '' : 'm'} ${faltam}. Para receber as próximas, responda ${opcoes.proximo.codigo} a.`]
|
|
214
|
+
: faltam ? [`Sobra${faltam === 1 ? '' : 'm'} ${faltam} para o próximo lote.`] : []),
|
|
215
|
+
...(lote.abertas
|
|
216
|
+
? [`${lote.abertas === 1 ? '1 pergunta aberta fica' : `${lote.abertas} perguntas abertas ficam`} para depois das objetivas, uma por vez.`]
|
|
217
|
+
: []),
|
|
218
|
+
...(opcoes.naoEsperam
|
|
219
|
+
? [`${opcoes.naoEsperam === 1 ? '1 pergunta do resumo já não esperava' : `${opcoes.naoEsperam} perguntas do resumo já não esperavam`} você quando fui buscar.`]
|
|
220
|
+
: []),
|
|
221
|
+
...(lote.recusadas.length ? [linhaDasRecusas(lote.recusadas.length)] : []),
|
|
222
|
+
].join('\n').slice(0, exports.TETO_DA_MENSAGEM);
|
|
223
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.nativeOfferSignature = nativeOfferSignature;
|
|
37
|
+
exports.provenNativeOffer = provenNativeOffer;
|
|
38
|
+
exports.readNativeOfferStdin = readNativeOfferStdin;
|
|
39
|
+
/** Prova efêmera de callback para apresentação. Não autoriza resposta nem gate. */
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const zod_1 = require("zod");
|
|
43
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
44
|
+
const hitl_native_1 = require("./hitl-native");
|
|
45
|
+
const offerSchema = zod_1.z.object({
|
|
46
|
+
native: hitl_native_1.nativeBindingSchema.extend({ messageId: zod_1.z.string().min(1).max(200).regex(/^[^\u0000-\u001f]+$/) }).strict(),
|
|
47
|
+
recebidoEm: zod_1.z.string().datetime(), prova: zod_1.z.string().regex(/^[a-f0-9]{64}$/),
|
|
48
|
+
}).strict();
|
|
49
|
+
function nativeOfferSignature(thread, pedido, offer, key) {
|
|
50
|
+
const n = offer.native;
|
|
51
|
+
return (0, node_crypto_1.createHmac)('sha256', key).update(JSON.stringify(['ork.hitl-native-offer/v1', thread, pedido,
|
|
52
|
+
n.host, n.installationId, n.connectionId, n.sessionId, n.accountId, n.channelId,
|
|
53
|
+
n.conversationId, n.personId, n.messageId, offer.recebidoEm])).digest('hex');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* I-41 (T4b): as duas versoes entram, e um fato consumado NUNCA sai provado.
|
|
57
|
+
*
|
|
58
|
+
* A prova serve para oferecer um caminho de resposta. `decidido` nao tem prazo e nao tem
|
|
59
|
+
* resposta: sem prazo nao existe a janela que esta funcao confere, e deixar `Date.parse('')`
|
|
60
|
+
* virar `NaN` faria a comparacao ser falsa e a oferta seguir adiante. Prazo ausente barra aqui,
|
|
61
|
+
* explicitamente, para que nenhum canal apareca `disponivel` para quem nao tem o que responder.
|
|
62
|
+
*/
|
|
63
|
+
function provenNativeOffer(pedido, value) {
|
|
64
|
+
try {
|
|
65
|
+
const prazo = (0, hitl_contract_1.prazoDoPedido)(pedido);
|
|
66
|
+
if (prazo === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
const offer = offerSchema.parse(value), n = offer.native, binding = (0, hitl_native_1.nativeBinding)(n.host);
|
|
69
|
+
const age = Date.now() - Date.parse(offer.recebidoEm);
|
|
70
|
+
if (age < 0 || age > 60000 || Date.now() >= Date.parse(prazo) ||
|
|
71
|
+
Object.entries(binding).some(([k, v]) => n[k] !== v))
|
|
72
|
+
return undefined;
|
|
73
|
+
const expected = nativeOfferSignature(pedido.thread, pedido.id, offer, (0, hitl_native_1.nativeKey)(n.host));
|
|
74
|
+
return (0, node_crypto_1.timingSafeEqual)(Buffer.from(expected, 'hex'), Buffer.from(offer.prova, 'hex')) ? n.host : undefined;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function readNativeOfferStdin() {
|
|
81
|
+
const buffer = Buffer.alloc(8193);
|
|
82
|
+
let total = 0;
|
|
83
|
+
for (;;) {
|
|
84
|
+
const count = fs.readSync(0, buffer, total, buffer.length - total, null);
|
|
85
|
+
total += count;
|
|
86
|
+
if (total > 8192)
|
|
87
|
+
throw Error('hitl.native.offer-limit');
|
|
88
|
+
if (!count)
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(buffer.subarray(0, total).toString('utf8'));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw Error('hitl.native.offer-invalid');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nativeContextSchema = exports.nativeBindingSchema = void 0;
|
|
4
|
+
exports.nativeBinding = nativeBinding;
|
|
5
|
+
exports.nativeKey = nativeKey;
|
|
6
|
+
exports.nativeSignature = nativeSignature;
|
|
7
|
+
exports.nativeMessage = nativeMessage;
|
|
8
|
+
exports.authenticateNative = authenticateNative;
|
|
9
|
+
/** Ingresso v3 exclusivo do callback do host. Nunca exposto como tool do modelo. */
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const zod_1 = require("zod");
|
|
12
|
+
const id = zod_1.z.string().min(1).max(200).regex(/^[^\u0000-\u001f]+$/);
|
|
13
|
+
exports.nativeBindingSchema = zod_1.z.object({
|
|
14
|
+
host: zod_1.z.enum(['hermes', 'openclaw']), installationId: id, connectionId: id,
|
|
15
|
+
sessionId: id, accountId: id, channelId: id, conversationId: id, personId: id,
|
|
16
|
+
}).strict();
|
|
17
|
+
exports.nativeContextSchema = exports.nativeBindingSchema.extend({
|
|
18
|
+
messageId: id, context: zod_1.z.string().regex(/^[a-f0-9]{64}$/),
|
|
19
|
+
pedidoSha256: zod_1.z.string().regex(/^[a-f0-9]{64}$/), expiresAt: zod_1.z.string().datetime(),
|
|
20
|
+
}).strict();
|
|
21
|
+
const prefix = (host) => host === 'hermes' ? 'HERMES' : host === 'openclaw' ? 'OPENCLAW' : (() => { throw Error('hitl.native.host'); })();
|
|
22
|
+
/** Configuração privada do processo de ingresso; não é argumento da ferramenta. */
|
|
23
|
+
function nativeBinding(host, env = process.env) {
|
|
24
|
+
try {
|
|
25
|
+
const value = exports.nativeBindingSchema.parse(JSON.parse(env[`ORK_HITL_NATIVE_BINDING_${prefix(host)}`] ?? 'null'));
|
|
26
|
+
if (value.host !== host)
|
|
27
|
+
throw Error('scope');
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw Error('hitl.native.capability-unavailable');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function nativeKey(host, env = process.env) {
|
|
35
|
+
const key = env[`ORK_HITL_NATIVE_KEY_${prefix(host)}`] ?? '';
|
|
36
|
+
const other = env[`ORK_HITL_NATIVE_KEY_${host === 'hermes' ? 'OPENCLAW' : 'HERMES'}`];
|
|
37
|
+
if (Buffer.byteLength(key) < 32 || key === other || key === env.ORK_HITL_INGRESS_KEY_HERMES || key === env.ORK_HITL_INGRESS_KEY_OPENCLAW)
|
|
38
|
+
throw Error('hitl.native.credentials-unavailable');
|
|
39
|
+
return key;
|
|
40
|
+
}
|
|
41
|
+
function nativeSignature(thread, pedido, r, key) {
|
|
42
|
+
const n = exports.nativeContextSchema.parse(r.native);
|
|
43
|
+
return (0, node_crypto_1.createHmac)('sha256', key).update(JSON.stringify(['ork.hitl-native/v1', thread, pedido,
|
|
44
|
+
n.host, n.installationId, n.connectionId, n.sessionId, n.accountId, n.channelId,
|
|
45
|
+
n.conversationId, n.personId, n.messageId, n.context, n.pedidoSha256, n.expiresAt,
|
|
46
|
+
r.origem, r.canal, r.conta, r.por, r.mensagem, r.recebidoEm, r.resposta])).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
function nativeMessage(n) {
|
|
49
|
+
return 'native:' + (0, node_crypto_1.createHash)('sha256').update(JSON.stringify([n.host, n.installationId,
|
|
50
|
+
n.connectionId, n.sessionId, n.channelId, n.accountId, n.conversationId, n.messageId])).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
function authenticateNative(thread, pedido, r, when = new Date().toISOString(), env = process.env) {
|
|
53
|
+
const n = exports.nativeContextSchema.parse(r.native), binding = nativeBinding(r.canal, env);
|
|
54
|
+
const age = Date.parse(when) - Date.parse(r.recebidoEm);
|
|
55
|
+
if (Object.entries(binding).some(([k, v]) => n[k] !== v) ||
|
|
56
|
+
r.origem !== 'native' || r.conta !== binding.accountId || r.por !== `native:${n.host}:${n.personId}` ||
|
|
57
|
+
r.mensagem !== nativeMessage(n) || !Number.isFinite(age) || age < 0 || age > 60000 ||
|
|
58
|
+
!Number.isFinite(Date.parse(n.expiresAt)) || Date.parse(r.recebidoEm) >= Date.parse(n.expiresAt) ||
|
|
59
|
+
typeof r.resposta !== 'string' || !r.resposta.length || r.resposta.length > 4096 || r.resposta.includes('\0') ||
|
|
60
|
+
!/^[a-f0-9]{64}$/.test(r.prova ?? ''))
|
|
61
|
+
throw Error('hitl.native.untrusted');
|
|
62
|
+
const expected = nativeSignature(thread, pedido, r, nativeKey(n.host, env));
|
|
63
|
+
if (!(0, node_crypto_1.timingSafeEqual)(Buffer.from(r.prova, 'hex'), Buffer.from(expected, 'hex')))
|
|
64
|
+
throw Error('hitl.native.untrusted');
|
|
65
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.prazoLocalDoPedido = prazoLocalDoPedido;
|
|
37
|
+
exports.apresentarDecisao = apresentarDecisao;
|
|
38
|
+
exports.apresentarHitl = apresentarHitl;
|
|
39
|
+
exports.pedidoHitlAberto = pedidoHitlAberto;
|
|
40
|
+
exports.ofertaDoPedido = ofertaDoPedido;
|
|
41
|
+
/** D5: preparação determinística e limitada, sem delegar leitura de artefatos ao host. */
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const thread_1 = require("./thread");
|
|
46
|
+
const estado_thread_1 = require("./estado-thread");
|
|
47
|
+
const claims_1 = require("./claims");
|
|
48
|
+
const ledger_1 = require("./ledger");
|
|
49
|
+
const hitl_1 = require("./hitl");
|
|
50
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
51
|
+
const hitl_canais_1 = require("./hitl-canais");
|
|
52
|
+
const hitl_native_offer_1 = require("./hitl-native-offer");
|
|
53
|
+
const horario_1 = require("./horario");
|
|
54
|
+
/**
|
|
55
|
+
* I-35: prazo do pedido no fuso do dono, absoluto e rotulado, para JSON de exibição
|
|
56
|
+
* (`ork gate context` e `ork_hitl_pending`). `pedido.prazo` continua o ISO assinado.
|
|
57
|
+
*/
|
|
58
|
+
function prazoLocalDoPedido(pedido, agora) {
|
|
59
|
+
return (0, horario_1.formatarDataHoraRotulada)((0, hitl_contract_1.prazoDoPedido)(pedido), { agora });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Apresentação não altera o pedido assinado nem escolhe uma resposta pelo humano.
|
|
63
|
+
*
|
|
64
|
+
* I-41: as duas versões saem por aqui. O texto de um `ork.hitl/v1` continua byte a byte o de
|
|
65
|
+
* antes; o que o v2 acrescenta é a consequência de cada alternativa e o porquê da recomendada,
|
|
66
|
+
* e o que ele troca é o número pela letra. Um `decidido` não passa por aqui de propósito: ele
|
|
67
|
+
* não tem escolha a oferecer, e montar um diálogo para um fato consumado é pedir ação por engano.
|
|
68
|
+
*/
|
|
69
|
+
function apresentarDecisao(pedido, agora) {
|
|
70
|
+
(0, hitl_contract_1.validarPedidoHitl)(pedido);
|
|
71
|
+
const aceita = (0, hitl_contract_1.respostaAceitaDoPedido)(pedido);
|
|
72
|
+
if (!aceita)
|
|
73
|
+
throw new Error('decisão informada não tem diálogo: ela não pede nada');
|
|
74
|
+
const livre = aceita.tipo === 'texto';
|
|
75
|
+
const escolhas = (0, hitl_contract_1.escolhasDoPedido)(pedido).map(o => ({ const: (0, hitl_contract_1.chaveDaEscolha)(pedido, o.numero), title: (0, hitl_1.redigirSegredos)(o.texto) }));
|
|
76
|
+
const v2 = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido : undefined;
|
|
77
|
+
const expiracao = (0, hitl_contract_1.expiracaoDoPedido)(pedido);
|
|
78
|
+
const linhas = [
|
|
79
|
+
`Orkastery · ${pedido.fase}`,
|
|
80
|
+
`• Decisão: ${(0, hitl_1.redigirSegredos)((0, hitl_contract_1.textoDoPedido)(pedido))}`,
|
|
81
|
+
`• Recomendo: ${(0, hitl_1.redigirSegredos)((0, hitl_contract_1.recomendacaoDoPedido)(pedido))}`,
|
|
82
|
+
...escolhas.flatMap((o, i) => [
|
|
83
|
+
` ${o.const}. ${o.title}`,
|
|
84
|
+
...(v2 ? [` → ${(0, hitl_1.redigirSegredos)(v2.alternativas[i].consequencia)}`] : []),
|
|
85
|
+
]),
|
|
86
|
+
livre ? '• Resposta: escreva sua resposta; o texto será enviado literalmente.'
|
|
87
|
+
: `• Resposta: selecione uma opção no diálogo. Se necessário, digite ${v2 ? 'a letra' : 'o número'}.`,
|
|
88
|
+
...(livre && escolhas.length ? [' Exemplo: digitar “2” envia o texto “2”, sem selecionar a segunda opção.'] : []),
|
|
89
|
+
`• Prazo: ${(0, horario_1.formatarPrazo)((0, hitl_contract_1.prazoDoPedido)(pedido), { rotulo: true, agora })}. Sem resposta, ${expiracao === 'esperar' ? 'aguardamos' : expiracao === 'seguir-recomendada' ? 'sigo com a recomendada' : 'o pedido será escalado'}.`,
|
|
90
|
+
];
|
|
91
|
+
return { mensagem: linhas.join('\n'), escolhas };
|
|
92
|
+
}
|
|
93
|
+
const limitar = (texto, limite) => {
|
|
94
|
+
const seguro = (0, hitl_1.redigirSegredos)(texto);
|
|
95
|
+
return seguro.length > limite ? seguro.slice(0, limite) + '\n[trecho limitado]' : seguro;
|
|
96
|
+
};
|
|
97
|
+
/** Somente arquivos regulares dentro do domicílio canônico; não segue link externo. */
|
|
98
|
+
function lerArtefato(dir, nome) {
|
|
99
|
+
const arquivo = path.join(dir, nome);
|
|
100
|
+
try {
|
|
101
|
+
const real = fs.realpathSync(arquivo);
|
|
102
|
+
if (!real.startsWith(fs.realpathSync(dir) + path.sep))
|
|
103
|
+
return '[artefato externo recusado]';
|
|
104
|
+
const fd = fs.openSync(real, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
105
|
+
try {
|
|
106
|
+
const stat = fs.fstatSync(fd);
|
|
107
|
+
if (!stat.isFile() || stat.size > 65536)
|
|
108
|
+
return '[artefato indisponível ou acima de 64 KiB]';
|
|
109
|
+
return fs.readFileSync(fd, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
fs.closeSync(fd);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* D1/T6: a profundidade vem do PEDIDO quando ha um; o modo so da o default.
|
|
121
|
+
*
|
|
122
|
+
* Antes, o bloco de evidencia era escolhido pelo modo da thread, e nem o emissor nem o dono
|
|
123
|
+
* tinham como pedir mais evidencia para uma pergunta que precisava dela. Quem chama passa a
|
|
124
|
+
* profundidade do pedido aberto; sem pedido, o default do modo continua valendo exatamente como
|
|
125
|
+
* antes, e e por isso que nenhuma superficie que nao conhece pedido muda de comportamento.
|
|
126
|
+
*/
|
|
127
|
+
function apresentarHitl(raiz, id, profundidadeDoPedido) {
|
|
128
|
+
const t = (0, thread_1.lerThread)(raiz, id), profundidade = profundidadeDoPedido ?? (0, hitl_contract_1.profundidadeDoModo)(t.modo);
|
|
129
|
+
const dir = (0, thread_1.dirThread)(raiz, id);
|
|
130
|
+
const artefato = lerArtefato(dir, `${t.faseAtual}.md`) || lerArtefato(dir, `${t.faseAtual.toLowerCase()}.md`);
|
|
131
|
+
const claims = (0, claims_1.lerClaims)(raiz, id).slice(0, 20).map(c => `${c.id}: ${c.estado}; ${c.alegacao}; verify: ${c.verificar.join(' && ')}`).join('\n');
|
|
132
|
+
const riscos = artefato.split('\n').filter(l => /risco|limita[cç][aã]o|pend[eê]n|bloque|n[aã]o comprovad/i.test(l)).join('\n');
|
|
133
|
+
let diff = '[diff indisponível]';
|
|
134
|
+
const cwd = t.worktree || raiz;
|
|
135
|
+
try {
|
|
136
|
+
if ((0, estado_thread_1.raizDoEstado)(cwd) === (0, estado_thread_1.raizDoEstado)(raiz) && /^[a-f0-9]{40}$/.test(t.base.commit)) {
|
|
137
|
+
const r = (0, node_child_process_1.spawnSync)('git', ['--no-pager', 'diff', '--no-ext-diff', '--no-textconv',
|
|
138
|
+
...(profundidade === 'profunda' ? ['--unified=2'] : ['--stat']), t.base.commit, 'HEAD', '--',
|
|
139
|
+
'core/src', 'adapters', 'scripts', 'docs', 'eval'], { cwd, encoding: 'utf8', timeout: 5000, maxBuffer: 256 * 1024 });
|
|
140
|
+
if (r.status === 0)
|
|
141
|
+
diff = r.stdout || '[sem diff de produto]';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch { /* falha de consulta permanece explícita */ }
|
|
145
|
+
return { profundidade, artefato: profundidade === 'resumo' ? '' : limitar(artefato || '[artefato ausente]', profundidade === 'profunda' ? 1200 : 500),
|
|
146
|
+
claims: limitar(claims || '[claims ausentes]', profundidade === 'resumo' ? 250 : 600),
|
|
147
|
+
riscos: limitar(riscos || '[riscos não descritos no artefato]', 350),
|
|
148
|
+
diff: profundidade === 'resumo' ? '' : limitar(diff, profundidade === 'profunda' ? 800 : 350) };
|
|
149
|
+
}
|
|
150
|
+
/** Leitura pura: nunca cria pedido ou aprovação durante uma consulta do pulse. */
|
|
151
|
+
function pedidoHitlAberto(raiz, id, fase, sessionId) {
|
|
152
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
153
|
+
const ultimoDespacho = eventos.filter(e => e.tipo === 'phase_dispatch').at(-1)?.ts ?? '';
|
|
154
|
+
for (const e of [...eventos].reverse()) {
|
|
155
|
+
if (e.tipo !== 'hitl_requested' || e.ts < ultimoDespacho)
|
|
156
|
+
continue;
|
|
157
|
+
try {
|
|
158
|
+
// I-41 (T4): as duas versoes sao oferecidas, e `decidido` NUNCA. Um fato consumado nao
|
|
159
|
+
// segura fase, nao tem caminho de resposta e nao pode aparecer como coisa a responder;
|
|
160
|
+
// `alvoDoPedido` devolve `undefined` para ele, e a linha abaixo o descarta por isso.
|
|
161
|
+
(0, hitl_contract_1.validarPedidoHitl)(e.pedido);
|
|
162
|
+
const p = e.pedido;
|
|
163
|
+
const alvo = (0, hitl_contract_1.alvoDoPedido)(p);
|
|
164
|
+
if (!alvo)
|
|
165
|
+
continue;
|
|
166
|
+
if (p.fase !== fase || (sessionId ? alvo.tipo !== 'session' || alvo.sessionId !== sessionId : alvo.tipo !== 'gate'))
|
|
167
|
+
continue;
|
|
168
|
+
if (!eventos.some(r => ['human_gate', 'session_answered'].includes(r.tipo) && r.pedidoId === p.id))
|
|
169
|
+
return p;
|
|
170
|
+
}
|
|
171
|
+
catch { /* pedido inválido não é oferecido como resposta */ }
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* FX6: a oferta de canais que acompanha um pedido HITL REAL.
|
|
177
|
+
*
|
|
178
|
+
* `ofertaDeCanais` existia desde D12 e era chamada de um lugar so: o canario. No caminho de
|
|
179
|
+
* producao o humano via a pergunta e um `comandoResposta`, e nada dizia por quais canais dava
|
|
180
|
+
* para responder AGORA nem por que os outros nao serviam. Uma oferta que so aparece em
|
|
181
|
+
* simulacao nao e oferta; e uma tabela bonita, como a propria narrativa de D12 admitia.
|
|
182
|
+
*
|
|
183
|
+
* As pre-condicoes sao reais e lidas na hora: as variaveis de cada canal saem do ambiente do
|
|
184
|
+
* processo que esta respondendo a consulta, e `conexoesMcp` traz apenas os hosts com conexao
|
|
185
|
+
* MCP viva conhecida por quem chama. O Pulse, que roda em cron e nao e uma conexao MCP,
|
|
186
|
+
* passa a lista vazia de proposito: dizer `disponivel` ali seria inventar uma conexao.
|
|
187
|
+
*/
|
|
188
|
+
function ofertaDoPedido(pedido, conexoesMcp = [], nativo) {
|
|
189
|
+
(0, hitl_contract_1.validarPedidoHitl)(pedido);
|
|
190
|
+
const channels = (0, hitl_canais_1.ofertaDeCanais)({ env: process.env, conexoesMcp });
|
|
191
|
+
if (nativo === undefined)
|
|
192
|
+
return channels;
|
|
193
|
+
const proved = (0, hitl_native_offer_1.provenNativeOffer)(pedido, nativo.callback);
|
|
194
|
+
// Reserva lida na hora da oferta: callback provada não anuncia canal que o ingresso recusaria.
|
|
195
|
+
// Ledger sem este pedido não prova ausência de reserva.
|
|
196
|
+
let reserva;
|
|
197
|
+
try {
|
|
198
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(nativo.raiz, pedido.thread));
|
|
199
|
+
reserva = eventos.some(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedido.id)
|
|
200
|
+
? (0, hitl_canais_1.reservaDoPedido)(eventos, pedido.id) : 'hitl.channel.unverified';
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
reserva = 'hitl.channel.unverified';
|
|
204
|
+
}
|
|
205
|
+
return [...channels, ...['hermes', 'openclaw'].map(host => {
|
|
206
|
+
const oferta = (0, hitl_canais_1.ofertaNativa)(host, proved === host);
|
|
207
|
+
return oferta.estado === 'disponivel' && reserva ? { ...oferta, estado: 'indisponivel', motivo: reserva } : oferta;
|
|
208
|
+
})];
|
|
209
|
+
}
|