@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,475 @@
|
|
|
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.abrirPedidoSessao = abrirPedidoSessao;
|
|
37
|
+
exports.responderSessao = responderSessao;
|
|
38
|
+
exports.responderSessaoLocal = responderSessaoLocal;
|
|
39
|
+
exports.reconciliarEnvioDeSessao = reconciliarEnvioDeSessao;
|
|
40
|
+
exports.controleNativo = controleNativo;
|
|
41
|
+
exports.superarSessao = superarSessao;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const codex_controller_1 = require("./adapters/codex-controller");
|
|
44
|
+
/** D4/D19: nenhuma sessão é encerrada por prefixo, nome, PID inferido ou mera ausência. */
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const node_crypto_1 = require("node:crypto");
|
|
47
|
+
const claude_bg_1 = require("./adapters/claude-bg");
|
|
48
|
+
const thread_1 = require("./thread");
|
|
49
|
+
const ledger_1 = require("./ledger");
|
|
50
|
+
const util_1 = require("./util");
|
|
51
|
+
const hitl_gates_1 = require("./hitl-gates");
|
|
52
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
53
|
+
const hitl_local_atestado_1 = require("./hitl-local-atestado");
|
|
54
|
+
const hitl_local_receipt_1 = require("./hitl-local-receipt");
|
|
55
|
+
const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
|
|
56
|
+
const hitl_1 = require("./hitl");
|
|
57
|
+
const hitl_canais_1 = require("./hitl-canais");
|
|
58
|
+
const codex_question_1 = require("./adapters/codex-question");
|
|
59
|
+
const uuidCompleto = (s) => /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(s);
|
|
60
|
+
const reciboDaProva = (r) => (0, node_crypto_1.createHash)('sha256').update(r.prova).digest('hex');
|
|
61
|
+
const mesmaInstancia = (a, b) => a.sessionId === b.sessionId && a.runtime === b.runtime && a.cwd === b.cwd && a.instancia === b.instancia;
|
|
62
|
+
function controleDeResposta(raiz, id, sessionId, runtime, controle) {
|
|
63
|
+
const ctl = controle ?? controleNativo(runtime, raiz, id, sessionId);
|
|
64
|
+
if (!ctl.enviar || !ctl.confirmar)
|
|
65
|
+
throw new Error('runtime.unavailable: adapter não comprova entrada e recibo na mesma sessão; attach/resume não são fallback');
|
|
66
|
+
return ctl;
|
|
67
|
+
}
|
|
68
|
+
function observarBloqueio(ctl, sessionId, runtime, cwd) {
|
|
69
|
+
const consulta = ctl.consultar();
|
|
70
|
+
if (!consulta.ok)
|
|
71
|
+
throw new Error('runtime.unavailable: consulta falhou; sessão preservada');
|
|
72
|
+
const matches = consulta.sessoes.filter(s => s.sessionId === sessionId);
|
|
73
|
+
const s = matches[0];
|
|
74
|
+
if (matches.length !== 1 || !s || s.runtime !== runtime || s.cwd !== cwd || s.estado !== 'blocked' ||
|
|
75
|
+
!s.instancia || !s.bloqueio)
|
|
76
|
+
throw new Error('runtime.unavailable: identidade, cwd e prompt bloqueado exatos não comprovados');
|
|
77
|
+
return { sessionId: s.sessionId, runtime: s.runtime, cwd: s.cwd, estado: s.estado, instancia: s.instancia, bloqueio: s.bloqueio, perguntaNativa: s.perguntaNativa };
|
|
78
|
+
}
|
|
79
|
+
/** Cria somente um pedido vinculado ao despacho corrente e ao prompt nativo observado. */
|
|
80
|
+
function abrirPedidoSessao(raiz, id, sessionId, opcoes, controle) {
|
|
81
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => {
|
|
82
|
+
if (!uuidCompleto(sessionId))
|
|
83
|
+
throw new Error('identidade exige UUID completo');
|
|
84
|
+
const t = (0, thread_1.lerThread)(raiz, id), dir = (0, thread_1.dirThread)(raiz, id), eventos = (0, ledger_1.lerLedger)(dir);
|
|
85
|
+
const d = eventos.filter(e => e.tipo === 'phase_dispatch').at(-1);
|
|
86
|
+
if (t.status !== 'aberta' || t.faseAtual !== opcoes.fase || !t.sessoes.some(s => s.sessionId === sessionId && s.fase === opcoes.fase && s.runtime === opcoes.runtime && s.verificada) ||
|
|
87
|
+
d?.sessionId !== sessionId || d?.fase !== opcoes.fase || d?.runtime !== opcoes.runtime ||
|
|
88
|
+
!eventos.some(e => e.tipo === 'phase_dispatch_verified' && e.sessionId === sessionId && e.encontrada === true)) {
|
|
89
|
+
throw new Error('sessão não corresponde ao despacho corrente verificado da thread/fase/runtime');
|
|
90
|
+
}
|
|
91
|
+
const ctl = controleDeResposta(raiz, id, sessionId, opcoes.runtime, controle), cwd = path.resolve(t.worktree ?? raiz);
|
|
92
|
+
const sessao = observarBloqueio(ctl, sessionId, opcoes.runtime, cwd);
|
|
93
|
+
const revalidada = observarBloqueio(ctl, sessionId, opcoes.runtime, cwd);
|
|
94
|
+
if (!mesmaInstancia(sessao, revalidada) || sessao.bloqueio !== revalidada.bloqueio)
|
|
95
|
+
throw new Error('prompt ou instância mudou durante o pedido');
|
|
96
|
+
const nativa = sessao.perguntaNativa;
|
|
97
|
+
if (!nativa || JSON.stringify(nativa) !== JSON.stringify(revalidada.perguntaNativa) ||
|
|
98
|
+
(0, codex_question_1.projetarPergunta)({ isBlocking: true, questions: [nativa] }).sha256 !== nativa.sha256)
|
|
99
|
+
throw new Error('runtime.unavailable: conteúdo nativo da pergunta não comprovado');
|
|
100
|
+
if (opcoes.pergunta !== undefined && opcoes.pergunta !== nativa.question)
|
|
101
|
+
throw new Error('pergunta do operador diverge do conteúdo nativo; consentimento recusado');
|
|
102
|
+
const escolhas = nativa.options.map((o, i) => ({ numero: i + 1, texto: `${o.label} — ${o.description}`, acao: 'responder' }));
|
|
103
|
+
if ((0, hitl_1.redigirSegredos)(nativa.question) !== nativa.question || escolhas.some(o => o.texto.length > 200 || (0, hitl_1.redigirSegredos)(o.texto) !== o.texto))
|
|
104
|
+
throw new Error('runtime.unavailable: conteúdo nativo não pode ser publicado integralmente neste canal');
|
|
105
|
+
const contexto = (0, hitl_gates_1.contextoHitl)(raiz, id);
|
|
106
|
+
const existente = eventos.find(e => e.tipo === 'hitl_requested' && e.contexto === contexto &&
|
|
107
|
+
e.sessao?.sessionId === sessionId &&
|
|
108
|
+
e.sessao.instancia === sessao.instancia && e.sessao.bloqueio === sessao.bloqueio);
|
|
109
|
+
if (existente) {
|
|
110
|
+
const p = existente.pedido;
|
|
111
|
+
(0, hitl_contract_1.validarPedidoHitl)(p);
|
|
112
|
+
if (p.pergunta !== nativa.question || opcoes.prazo && p.prazo !== opcoes.prazo)
|
|
113
|
+
throw new Error('prompt já tem pedido; conteúdo divergente recusado');
|
|
114
|
+
return p; // inclusive expirado/respondido: não cria novo envio para o mesmo prompt
|
|
115
|
+
}
|
|
116
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
117
|
+
// O tipo aceito decide o que o canal orienta E o que o receptor recebe: uma coisa só.
|
|
118
|
+
const tipoAceito = escolhas.length && !nativa.isOther ? 'opcao' : 'texto';
|
|
119
|
+
// I-41 (T4d): a pergunta de SESSAO continua em `ork.hitl/v1`, e a razao e dura.
|
|
120
|
+
//
|
|
121
|
+
// O PLAN previa mover os dois emissores para v2. O gate deu certo porque as tres opcoes sao
|
|
122
|
+
// NOSSAS e a recomendada sai de fato registrado no ledger. Aqui nao: a pergunta e as opcoes
|
|
123
|
+
// vem do HOST, pelo prompt nativo. O v2 exige de 2 a 4 alternativas e EXATAMENTE UMA
|
|
124
|
+
// recomendada com o porque; uma pergunta nativa de texto livre nao tem alternativa nenhuma,
|
|
125
|
+
// uma com sete opcoes nao cabe em a-d, e o nucleo nao tem base para eleger a melhor entre
|
|
126
|
+
// opcoes que ele nao escreveu. Emitir v2 aqui exigiria inventar uma recomendada no chute,
|
|
127
|
+
// que e exatamente o defeito que esta thread existe para consertar; a alternativa seria
|
|
128
|
+
// recusar perguntas de sessao legitimas. v1 continua lido para sempre, e e o contrato certo
|
|
129
|
+
// para um conteudo que nao e nosso. Uma pergunta aberta tambem nao pertence a camada 2, que
|
|
130
|
+
// e de perguntas objetivas de cinco em cinco.
|
|
131
|
+
const pedido = { contrato: 'ork.hitl/v1', id: (0, node_crypto_1.randomUUID)(), thread: id, fase: opcoes.fase,
|
|
132
|
+
modo: t.modo, alvo: { tipo: 'session', sessionId, runtime: opcoes.runtime }, motivo: 'hitl.pergunta',
|
|
133
|
+
pergunta: nativa.question, opcoes: escolhas, recomendacao: orientacaoDaResposta(tipoAceito, escolhas.length),
|
|
134
|
+
criadoEm: quando, prazo: opcoes.prazo ?? new Date(Date.parse(quando) + 3600000).toISOString(),
|
|
135
|
+
acaoPadraoAoExpirar: 'esperar', respostaAceita: { tipo: tipoAceito, maxCaracteres: 4096 }, profundidade: (0, hitl_contract_1.profundidadeDoModo)(t.modo) };
|
|
136
|
+
(0, hitl_contract_1.validarPedidoHitl)(pedido);
|
|
137
|
+
(0, ledger_1.registrar)(dir, id, 'hitl_requested', { fase: pedido.fase, pedido, contexto, sessao,
|
|
138
|
+
evidencia: 'duas consultas do adapter concordam com despacho, instância, cwd e prompt bloqueado' });
|
|
139
|
+
return pedido;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A orientação do canal diz exatamente o que o receptor vai receber, e nada além disso.
|
|
144
|
+
* Quando a pergunta nativa aceita texto, um número digitado continua sendo esse texto.
|
|
145
|
+
*/
|
|
146
|
+
function orientacaoDaResposta(tipo, quantasOpcoes) {
|
|
147
|
+
const fecho = 'A resposta não aprova gates do bloco.';
|
|
148
|
+
if (tipo === 'opcao')
|
|
149
|
+
return `Responda com o número de uma das ${quantasOpcoes} opções desta sessão; o receptor recebe o rótulo nativo correspondente. ${fecho}`;
|
|
150
|
+
const contexto = quantasOpcoes
|
|
151
|
+
? ` As opções listadas são o conteúdo nativo da pergunta, não um seletor: digitar um número envia esse número.`
|
|
152
|
+
: '';
|
|
153
|
+
return `Esta pergunta aceita texto livre: o receptor recebe a sua resposta literal, inclusive quando ela for um número.${contexto} ${fecho}`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* O núcleo da entrega, comum aos quatro canais: conferência do vínculo nativo, reserva
|
|
157
|
+
* durável, envio único e confirmação correlacionada. Nenhum canal ganha atalho aqui.
|
|
158
|
+
*/
|
|
159
|
+
function entregarNaSessao(raiz, id, pedido, prov, controle) {
|
|
160
|
+
if (pedido.alvo.tipo !== 'session')
|
|
161
|
+
throw new Error('pedido destina-se ao gate, não à sessão');
|
|
162
|
+
const pedidoId = pedido.id;
|
|
163
|
+
const { sessionId, runtime } = pedido.alvo;
|
|
164
|
+
const resultado = { ok: true, pedidoId, sessionId, estado: 'entregue' };
|
|
165
|
+
const dir = (0, thread_1.dirThread)(raiz, id), eventos = (0, ledger_1.lerLedger)(dir);
|
|
166
|
+
const solicitado = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
167
|
+
const sessao = solicitado?.sessao;
|
|
168
|
+
if (!sessao || !uuidCompleto(sessionId) || sessao.sessionId !== sessionId || sessao.runtime !== runtime ||
|
|
169
|
+
!sessao.instancia || !sessao.bloqueio || sessao.cwd !== path.resolve((0, thread_1.lerThread)(raiz, id).worktree ?? raiz)) {
|
|
170
|
+
throw new Error('runtime.unavailable: pedido sem vínculo nativo exato');
|
|
171
|
+
}
|
|
172
|
+
const ctl = controleDeResposta(raiz, id, sessionId, runtime, controle);
|
|
173
|
+
const pendente = eventos.find(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId);
|
|
174
|
+
if (pendente && (pendente.recibo !== prov.recibo || pendente.mensagem !== prov.mensagem || pendente.autorizadoPor !== prov.autorizadoPor)) {
|
|
175
|
+
throw new Error('envio já iniciado; resposta divergente recusada');
|
|
176
|
+
}
|
|
177
|
+
if (eventos.some(e => e.tipo === 'session_answer_sending' && e.pedidoId !== pedidoId && e.mensagem === prov.mensagem)) {
|
|
178
|
+
throw new Error('mensagem já reservada para outro pedido');
|
|
179
|
+
}
|
|
180
|
+
const entrega = { contrato: 'ork.session-answer/v1',
|
|
181
|
+
envioId: pendente ? String(pendente.envioId) : (0, node_crypto_1.randomUUID)(), pedidoId, thread: id, fase: pedido.fase, sessao, recibo: prov.recibo };
|
|
182
|
+
if (!pendente) {
|
|
183
|
+
for (let i = 0; i < 2; i++) {
|
|
184
|
+
const atual = observarBloqueio(ctl, sessionId, runtime, sessao.cwd);
|
|
185
|
+
if (!mesmaInstancia(atual, sessao) || atual.bloqueio !== sessao.bloqueio || JSON.stringify(atual.perguntaNativa) !== JSON.stringify(sessao.perguntaNativa))
|
|
186
|
+
throw new Error('prompt ou instância mudou antes do envio');
|
|
187
|
+
}
|
|
188
|
+
(0, ledger_1.registrar)(dir, id, 'session_answer_sending', { ...prov.dados, sessionId, runtime,
|
|
189
|
+
instancia: sessao.instancia, bloqueio: sessao.bloqueio, envioId: entrega.envioId });
|
|
190
|
+
// O número só vira rótulo nativo quando o pedido aceita OPÇÃO. Em texto livre a
|
|
191
|
+
// resposta humana chega literal ao receptor, inclusive quando é "1" ou "12".
|
|
192
|
+
const numero = pedido.respostaAceita.tipo === 'opcao' && /^(?:[1-9]|1[0-2])$/.test(prov.resposta.trim())
|
|
193
|
+
? Number(prov.resposta.trim()) - 1 : -1;
|
|
194
|
+
const respostaNativa = (numero < 0 ? undefined : sessao.perguntaNativa?.options[numero]?.label) ?? prov.resposta;
|
|
195
|
+
try {
|
|
196
|
+
ctl.enviar({ ...entrega, resposta: respostaNativa });
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
throw new Error('runtime.unavailable: resultado do envio incerto; não reenviar; consultar recibo do controller');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let ack;
|
|
203
|
+
try {
|
|
204
|
+
ack = ctl.confirmar(entrega);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
throw new Error('runtime.unavailable: consulta do recibo falhou; envio não será repetido');
|
|
208
|
+
}
|
|
209
|
+
if (!ack || ack.estado !== 'recebida' || ack.contrato !== entrega.contrato || ack.envioId !== entrega.envioId ||
|
|
210
|
+
ack.pedidoId !== pedidoId || ack.thread !== id || ack.fase !== pedido.fase || ack.recibo !== entrega.recibo ||
|
|
211
|
+
!ack.sessao || !mesmaInstancia(ack.sessao, sessao) || ack.sessao.bloqueio !== sessao.bloqueio) {
|
|
212
|
+
throw new Error('runtime.unavailable: receptor não confirmou recebimento correlacionado na mesma sessão; não reenviar');
|
|
213
|
+
}
|
|
214
|
+
const evidencia = prov.evidenciar?.({ ...entrega, sessao });
|
|
215
|
+
(0, ledger_1.registrar)(dir, id, 'session_answered', { ...prov.dados, sessionId, runtime,
|
|
216
|
+
instancia: sessao.instancia, bloqueio: sessao.bloqueio, envioId: entrega.envioId, estado: 'entregue',
|
|
217
|
+
...(evidencia ? { evidencia: evidencia.arquivo, evidenciaSha256: evidencia.sha256, recibo: evidencia.recibo } : {
|
|
218
|
+
evidencia: 'recibo do controller confirma envioId, pedido, thread/fase, UUID, instância, cwd, prompt e envelope'
|
|
219
|
+
}) });
|
|
220
|
+
return { ...resultado, repetida: !!pendente };
|
|
221
|
+
}
|
|
222
|
+
/** Uma intenção durável impede reenvio após crash, timeout ou confirmação divergente. */
|
|
223
|
+
function responderSessao(raiz, id, pedidoId, r, controle, quando) {
|
|
224
|
+
(0, hitl_gates_1.receberResposta)(raiz, id, pedidoId, r, quando);
|
|
225
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => {
|
|
226
|
+
const { pedido, anterior } = (0, hitl_gates_1.conferirResposta)(raiz, id, pedidoId, r, quando);
|
|
227
|
+
if (pedido.alvo.tipo !== 'session')
|
|
228
|
+
throw new Error('pedido destina-se ao gate, não à sessão');
|
|
229
|
+
if (anterior)
|
|
230
|
+
return { ok: true, pedidoId, sessionId: pedido.alvo.sessionId, estado: 'entregue', repetida: true };
|
|
231
|
+
return entregarNaSessao(raiz, id, pedido, { dados: (0, hitl_gates_1.dadosDaResposta)(pedido, r), resposta: r.resposta,
|
|
232
|
+
recibo: reciboDaProva(r), mensagem: r.mensagem, autorizadoPor: r.por,
|
|
233
|
+
// FX4: o ingresso duravel ja foi gravado e autenticado por `receberResposta`. Sem
|
|
234
|
+
// referencia-lo no `session_answered`, a prova existia em disco e nao chegava ao
|
|
235
|
+
// recibo: metade do HITL ficava com a palavra do controller e nada mais.
|
|
236
|
+
evidenciar: () => ({ ...(0, hitl_ingress_receipt_1.evidenciaDoIngresso)(raiz, id, pedidoId, r), recibo: reciboDaProva(r) }) }, controle);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* D12: responder a pergunta nativa de uma sessão pelo canal MCP local.
|
|
241
|
+
*
|
|
242
|
+
* O HITL do Orkastery não é só gate de fase. Uma sessão bloqueada numa pergunta nativa é
|
|
243
|
+
* HITL igual, e antes desta função ela só podia ser respondida pelo Telegram: Codex e
|
|
244
|
+
* Claude Code tinham `mcp-local` para decidir bloco e não tinham para responder à própria
|
|
245
|
+
* sessão que o dono estava olhando. O canal, a correlação e o recibo vêm da CONEXÃO, nunca
|
|
246
|
+
* de argumento de ferramenta: é isso que impede o agente de responder por quem pediu.
|
|
247
|
+
*
|
|
248
|
+
* Aceita texto livre quando a pergunta nativa aceita, porque uma pergunta aberta respondida
|
|
249
|
+
* por menu vira outra pergunta.
|
|
250
|
+
*/
|
|
251
|
+
function responderSessaoLocal(raiz, id, pedidoId, token, controle) {
|
|
252
|
+
const atestado = (0, hitl_local_atestado_1.consumirAtestadoLocal)(token, raiz, id, pedidoId);
|
|
253
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => {
|
|
254
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
|
|
255
|
+
const evento = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
256
|
+
(0, hitl_contract_1.validarPedidoHitl)(evento?.pedido);
|
|
257
|
+
const pedido = evento.pedido;
|
|
258
|
+
if (pedido.thread !== id || pedido.alvo.tipo !== 'session')
|
|
259
|
+
throw new Error('hitl.local.somente-sessoes');
|
|
260
|
+
const anterior = eventos.find(e => e.tipo === 'session_answered' && e.pedidoId === pedidoId);
|
|
261
|
+
if (anterior)
|
|
262
|
+
return { ok: true, pedidoId, sessionId: pedido.alvo.sessionId, estado: 'entregue', repetida: true };
|
|
263
|
+
(0, hitl_canais_1.conferirCanalSelecionado)(eventos, pedidoId, atestado.host, 'mcp-local', atestado.connectionId, atestado.requestId);
|
|
264
|
+
if (evento.contexto !== atestado.contexto || (0, hitl_gates_1.contextoHitl)(raiz, id) !== atestado.contexto ||
|
|
265
|
+
(0, hitl_local_atestado_1.hashPedidoLocal)(pedido) !== atestado.pedidoSha256)
|
|
266
|
+
throw new Error('hitl.local.pedido-antigo');
|
|
267
|
+
// Opção numerada continua sendo conferida contra o pedido; texto livre passa pelo
|
|
268
|
+
// mesmo limite de tamanho. Em nenhum dos dois casos o corpo é gravado.
|
|
269
|
+
(0, hitl_contract_1.validarRespostaHitl)(pedido, atestado.opcao);
|
|
270
|
+
const identidade = `mcp-local:${atestado.connectionId}:${atestado.requestId}`;
|
|
271
|
+
const dados = { contrato: pedido.contrato, pedidoId, fase: pedido.fase, origem: 'mcp-local',
|
|
272
|
+
source: 'human', proveniencia: 'ork.mcp-elicitation/v1',
|
|
273
|
+
canal: atestado.host, contratoResposta: 'ork.hitl-local-session/v1',
|
|
274
|
+
autorizadoPor: `mcp-local:${atestado.host}`, conexao: atestado.connectionId,
|
|
275
|
+
solicitacao: atestado.requestId, contexto: atestado.contexto, pedidoSha256: atestado.pedidoSha256,
|
|
276
|
+
recebidoEm: atestado.recebidoEm, mensagem: identidade,
|
|
277
|
+
// Amarra conteudo: o recibo que o controller confirmou deriva do hash da resposta.
|
|
278
|
+
reciboEntrega: (0, hitl_local_atestado_1.reciboDoAtestado)(atestado) };
|
|
279
|
+
return entregarNaSessao(raiz, id, pedido, {
|
|
280
|
+
dados, resposta: atestado.opcao, mensagem: identidade,
|
|
281
|
+
autorizadoPor: `mcp-local:${atestado.host}`,
|
|
282
|
+
// O recibo que o controller confirma amarra o CONTEUDO, nao so a conexao.
|
|
283
|
+
recibo: (0, hitl_local_atestado_1.reciboDoAtestado)(atestado),
|
|
284
|
+
evidenciar: (entrega) => (0, hitl_local_receipt_1.gravarEvidenciaLocalSessao)(atestado, { fase: String(pedido.fase),
|
|
285
|
+
sessionId: entrega.sessao.sessionId, runtime: entrega.sessao.runtime,
|
|
286
|
+
instancia: entrega.sessao.instancia, bloqueio: String(entrega.sessao.bloqueio), envioId: entrega.envioId }),
|
|
287
|
+
}, controle);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* FX3: reconciliar um `session_answer_sending` orfao, sem o envelope original.
|
|
292
|
+
*
|
|
293
|
+
* O buraco que C-CHECK3 nomeou: a reserva duravel guardava `sha(prova)`, nunca a `prova`.
|
|
294
|
+
* Isso esta certo e continua assim. O problema era outro: o UNICO caminho que fechava um
|
|
295
|
+
* envio pendente exigia que o MESMO chamador reapresentasse a `RespostaHumana` inteira. Se
|
|
296
|
+
* o processo que originou o envio morresse com o `r` so na memoria, o pendente ficava para
|
|
297
|
+
* sempre: nao dava para confirmar, nem para saber se o receptor recebeu, e qualquer
|
|
298
|
+
* tentativa nova batia em "envio ja iniciado; resposta divergente recusada".
|
|
299
|
+
*
|
|
300
|
+
* Aqui a reconciliacao usa apenas o que ESTA no ledger: `envioId`, `recibo`, `mensagem`,
|
|
301
|
+
* `autorizadoPor` e o vinculo nativo do pedido. Com isso ela reconstroi a entrega e pergunta
|
|
302
|
+
* ao receptor, pelo recibo correlacionado, se ele recebeu aquele `envioId`. Ela NUNCA envia
|
|
303
|
+
* nada: reenviar as cegas e justamente o que a reserva duravel existe para impedir.
|
|
304
|
+
*
|
|
305
|
+
* Nao existe desfecho "cancelado". Nada observavel prova que o receptor NAO recebeu, e
|
|
306
|
+
* inventar essa prova seria o oposto do que este nucleo faz. Sem confirmacao, o pendente
|
|
307
|
+
* continua pendente, e a proxima chamada tenta de novo, sem efeito acumulado.
|
|
308
|
+
*/
|
|
309
|
+
function reconciliarEnvioDeSessao(raiz, id, pedidoId, controle) {
|
|
310
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => {
|
|
311
|
+
const dir = (0, thread_1.dirThread)(raiz, id), eventos = (0, ledger_1.lerLedger)(dir);
|
|
312
|
+
const solicitado = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedidoId);
|
|
313
|
+
(0, hitl_contract_1.validarPedidoHitl)(solicitado?.pedido);
|
|
314
|
+
const pedido = solicitado.pedido;
|
|
315
|
+
if (pedido.thread !== id || pedido.alvo.tipo !== 'session')
|
|
316
|
+
throw new Error('hitl.sessao.pedido-nao-e-de-sessao');
|
|
317
|
+
const { sessionId, runtime } = pedido.alvo;
|
|
318
|
+
const respondido = eventos.find(e => e.tipo === 'session_answered' && e.pedidoId === pedidoId);
|
|
319
|
+
const pendente = eventos.find(e => e.tipo === 'session_answer_sending' && e.pedidoId === pedidoId);
|
|
320
|
+
if (!pendente)
|
|
321
|
+
throw new Error('hitl.sessao.sem-envio-pendente');
|
|
322
|
+
const envioId = String(pendente.envioId);
|
|
323
|
+
// Idempotencia: ja respondido nao reabre envio, nao consulta receptor e nao regrava.
|
|
324
|
+
if (respondido) {
|
|
325
|
+
return { ok: true, pedidoId, sessionId, envioId, estado: 'entregue', repetida: true,
|
|
326
|
+
prova: respondido.evidenciaSha256 ? 'ingresso-duravel' : 'recibo-do-controller' };
|
|
327
|
+
}
|
|
328
|
+
const sessao = solicitado.sessao;
|
|
329
|
+
if (!sessao || sessao.sessionId !== sessionId || sessao.runtime !== runtime || !sessao.instancia || !sessao.bloqueio ||
|
|
330
|
+
sessao.cwd !== path.resolve((0, thread_1.lerThread)(raiz, id).worktree ?? raiz)) {
|
|
331
|
+
throw new Error('runtime.unavailable: pedido sem vinculo nativo exato');
|
|
332
|
+
}
|
|
333
|
+
const ctl = controleDeResposta(raiz, id, sessionId, runtime, controle);
|
|
334
|
+
const entrega = { contrato: 'ork.session-answer/v1', envioId,
|
|
335
|
+
pedidoId, thread: id, fase: pedido.fase, sessao, recibo: String(pendente.recibo) };
|
|
336
|
+
let ack;
|
|
337
|
+
try {
|
|
338
|
+
ack = ctl.confirmar(entrega);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
throw new Error('runtime.unavailable: consulta do recibo falhou; envio nao sera repetido');
|
|
342
|
+
}
|
|
343
|
+
const confirmado = !!ack && ack.estado === 'recebida' && ack.contrato === entrega.contrato &&
|
|
344
|
+
ack.envioId === envioId && ack.pedidoId === pedidoId && ack.thread === id && ack.fase === pedido.fase &&
|
|
345
|
+
ack.recibo === entrega.recibo && !!ack.sessao && mesmaInstancia(ack.sessao, sessao) &&
|
|
346
|
+
ack.sessao.bloqueio === sessao.bloqueio;
|
|
347
|
+
const { ts: _ts, thread: _thread, tipo: _tipo, eventId: _eventId, ...origem } = pendente;
|
|
348
|
+
if (!confirmado) {
|
|
349
|
+
(0, ledger_1.registrar)(dir, id, 'session_answer_reconciled', { pedidoId, fase: pedido.fase, sessionId, runtime, envioId,
|
|
350
|
+
estado: 'pendente', decididoPor: 'nucleo ork',
|
|
351
|
+
razao: 'receptor nao confirmou recebimento correlacionado; envio preservado e nunca repetido' });
|
|
352
|
+
return { ok: false, pedidoId, sessionId, envioId, estado: 'pendente', repetida: false, prova: 'nenhuma' };
|
|
353
|
+
}
|
|
354
|
+
// A evidencia duravel do canal, quando existe, e derivavel SO do ledger: o nome do
|
|
355
|
+
// arquivo sai de `mensagem`. FX10: derivar nao e provar. O evento que esta prestes a ser
|
|
356
|
+
// gravado passa pelo MESMO MAC que o sync exige, ANTES de existir no ledger. Sem isso um
|
|
357
|
+
// byte trocado no pendente (ou no proprio arquivo de ingresso) virava `session_answered`
|
|
358
|
+
// com etiqueta de prova duravel, e a mentira so aparecia dias depois, no sync.
|
|
359
|
+
const evidencia = evidenciaReconciliada(raiz, id, origem);
|
|
360
|
+
const candidato = { ...origem, estado: 'entregue', reconciliado: true, ...evidencia.dados };
|
|
361
|
+
if (evidencia.prova === 'sem-prova-duravel' ||
|
|
362
|
+
evidencia.prova === 'ingresso-duravel' && !(0, hitl_ingress_receipt_1.validarEvidenciaReconstruida)(raiz, id, candidato)) {
|
|
363
|
+
// Falha FECHADA: nenhuma resposta gravada, o pendente continua pendente e a recusa
|
|
364
|
+
// fica auditavel. Reconciliar de novo com o estado consertado ainda funciona.
|
|
365
|
+
(0, ledger_1.registrar)(dir, id, 'session_answer_reconciled', { pedidoId, fase: pedido.fase, sessionId, runtime, envioId,
|
|
366
|
+
estado: 'recusado', decididoPor: 'nucleo ork',
|
|
367
|
+
razao: evidencia.prova === 'sem-prova-duravel'
|
|
368
|
+
? 'envio por telegram sem ingresso duravel correspondente; nenhuma resposta foi gravada'
|
|
369
|
+
: 'evidencia duravel reconstruida nao revalidou o MAC do canal; nenhuma resposta foi gravada' });
|
|
370
|
+
throw new Error('hitl.sessao.evidencia-duravel-invalida: envio pendente nao revalida; nada foi gravado');
|
|
371
|
+
}
|
|
372
|
+
(0, ledger_1.registrar)(dir, id, 'session_answer_reconciled', { pedidoId, fase: pedido.fase, sessionId, runtime, envioId,
|
|
373
|
+
estado: 'entregue', decididoPor: 'nucleo ork',
|
|
374
|
+
razao: 'recibo do controller confirmou envioId, pedido, thread/fase, instancia e prompt, e o MAC duravel revalidou' });
|
|
375
|
+
(0, ledger_1.registrar)(dir, id, 'session_answered', candidato);
|
|
376
|
+
return { ok: true, pedidoId, sessionId, envioId, estado: 'entregue', repetida: false, prova: evidencia.prova };
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/** Referencia duravel reconstruida so com o que o ledger ja guardava do envio pendente. */
|
|
380
|
+
function evidenciaReconciliada(raiz, id, origem) {
|
|
381
|
+
const mensagem = typeof origem.mensagem === 'string' ? origem.mensagem : '';
|
|
382
|
+
const nome = (0, node_crypto_1.createHash)('sha256').update(mensagem).digest('hex') + '.json';
|
|
383
|
+
const file = path.join((0, thread_1.dirThread)(raiz, id), 'hitl-ingress', nome);
|
|
384
|
+
if (origem.origem === 'telegram' || origem.origem === 'native') {
|
|
385
|
+
// FX10: um envio de telegram SEM ingresso duravel nao pode cair no recibo do controller.
|
|
386
|
+
// Esse desvio transformava o sumico do arquivo de prova em um caminho mais permissivo,
|
|
387
|
+
// que e exatamente o contrario do que apagar a prova deveria causar.
|
|
388
|
+
if (!mensagem || !fs.existsSync(file))
|
|
389
|
+
return { prova: 'sem-prova-duravel', dados: {} };
|
|
390
|
+
const bytes = fs.readFileSync(file, 'utf8');
|
|
391
|
+
return { prova: 'ingresso-duravel', dados: { evidencia: `.orkastery/threads/${id}/hitl-ingress/${nome}`,
|
|
392
|
+
evidenciaSha256: (0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex') } };
|
|
393
|
+
}
|
|
394
|
+
// O ingresso MCP local grava o recibo duravel DEPOIS da confirmacao, e o atestado de uso
|
|
395
|
+
// unico morreu com o processo. Reconciliar nao reemite esse recibo: diz, com nome, que a
|
|
396
|
+
// prova aqui e o recibo do controller, e nao uma evidencia duravel que ninguem assinou.
|
|
397
|
+
return { prova: 'recibo-do-controller', dados: { evidencia: 'reconciliacao: recibo do controller confirma envioId, pedido, thread/fase, instancia e prompt; evidencia duravel do ingresso nao foi reemitida' } };
|
|
398
|
+
}
|
|
399
|
+
function controleNativo(runtime, raiz, id, sessionId) {
|
|
400
|
+
if (runtime === 'codex') {
|
|
401
|
+
const t = (0, thread_1.lerThread)(raiz, id), sessao = t.sessoes.find(s => s.sessionId === sessionId && s.runtime === runtime);
|
|
402
|
+
const d = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id)).find(e => e.tipo === 'phase_dispatch' && e.sessionId === sessionId);
|
|
403
|
+
if (sessao?.controlador && d?.controlador === sessao.controlador && d.promptSha256 === sessao.promptSha256) {
|
|
404
|
+
const base = fs.realpathSync(path.join((0, thread_1.dirThread)(raiz, id), 'sessoes')) + path.sep;
|
|
405
|
+
const dir = fs.realpathSync(sessao.controlador);
|
|
406
|
+
if (!dir.startsWith(base))
|
|
407
|
+
throw new Error('runtime.unavailable: controller fora do despacho canônico');
|
|
408
|
+
return (0, codex_controller_1.controleDoController)(dir, { thread: id, fase: sessao.fase, promptSha256: sessao.promptSha256 }, sessionId, path.resolve(t.worktree ?? raiz));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (runtime !== 'claude-bg') {
|
|
412
|
+
throw new Error('runtime.unavailable: Codex exige identidade de controller vinculada pelo adapter; nenhum PID será inferido ou sinalizado');
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
consultar: () => {
|
|
416
|
+
const r = (0, claude_bg_1.consultarSessoes)(undefined, true);
|
|
417
|
+
return { ok: r.ok, sessoes: r.sessoes.map(s => ({ sessionId: s.sessionId, runtime, cwd: s.cwd ?? '',
|
|
418
|
+
estado: s.state ?? s.status ?? '', instancia: s.id && Number.isFinite(s.startedAt) ? JSON.stringify([s.id, s.startedAt]) : '' })) };
|
|
419
|
+
},
|
|
420
|
+
parar: s => (0, util_1.exec)('claude', ['stop', s.sessionId], undefined, 10000).ok,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/** Somente despacho posterior verificado torna uma sessão da mesma fase superável. */
|
|
424
|
+
function superarSobLock(raiz, id, sessionId, fase, runtime, controle) {
|
|
425
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(sessionId))
|
|
426
|
+
throw new Error('identidade exige UUID completo');
|
|
427
|
+
const t = (0, thread_1.lerThread)(raiz, id), dir = (0, thread_1.dirThread)(raiz, id), eventos = (0, ledger_1.lerLedger)(dir);
|
|
428
|
+
if (!t.sessoes.some(s => s.sessionId === sessionId && s.fase === fase && s.runtime === runtime)) {
|
|
429
|
+
throw new Error('sessão não pertence à thread/fase/runtime informados');
|
|
430
|
+
}
|
|
431
|
+
const despachos = eventos.filter(e => e.tipo === 'phase_dispatch');
|
|
432
|
+
const i = despachos.findIndex(e => e.sessionId === sessionId && e.fase === fase && e.runtime === runtime);
|
|
433
|
+
const posterior = despachos.slice(i + 1).find(e => e.sessionId !== sessionId && e.fase === fase &&
|
|
434
|
+
eventos.some(v => v.tipo === 'phase_dispatch_verified' && v.sessionId === e.sessionId && v.encontrada === true));
|
|
435
|
+
if (i < 0 || !posterior)
|
|
436
|
+
throw new Error('superação exige despacho posterior da mesma fase verificado no runtime');
|
|
437
|
+
const ctl = controle ?? controleNativo(runtime, raiz, id, sessionId);
|
|
438
|
+
const observar = () => {
|
|
439
|
+
const r = ctl.consultar();
|
|
440
|
+
if (!r.ok)
|
|
441
|
+
throw new Error('runtime.unavailable: consulta falhou; alerta e sessão preservados');
|
|
442
|
+
const matches = r.sessoes.filter(s => s.sessionId === sessionId);
|
|
443
|
+
if (matches.length > 1)
|
|
444
|
+
throw new Error('identidade ambígua no runtime');
|
|
445
|
+
return matches[0];
|
|
446
|
+
};
|
|
447
|
+
const antes = observar();
|
|
448
|
+
const recibo = eventos.filter(e => e.tipo === 'session_superseded' && e.sessionId === sessionId && e.fase === fase && e.runtime === runtime).at(-1);
|
|
449
|
+
const terminou = (s) => !s || ['stopped', 'done', 'completed', 'exited'].includes(s.estado);
|
|
450
|
+
if (recibo && terminou(antes))
|
|
451
|
+
return { ok: true, sessionId, repetida: true };
|
|
452
|
+
if (!antes || antes.estado !== 'blocked' || antes.runtime !== runtime || !antes.instancia || !antes.cwd ||
|
|
453
|
+
path.resolve(antes.cwd) !== path.resolve(t.worktree ?? raiz))
|
|
454
|
+
throw new Error('sessão bloqueada com identidade e cwd correspondentes não comprovada');
|
|
455
|
+
const revalidada = observar();
|
|
456
|
+
if (JSON.stringify(revalidada) !== JSON.stringify(antes))
|
|
457
|
+
throw new Error('identidade ou estado mudou antes do encerramento');
|
|
458
|
+
(0, ledger_1.registrar)(dir, id, 'session_superseding', { fase, sessionId, runtime, instancia: antes.instancia, cwd: antes.cwd,
|
|
459
|
+
superadaPor: posterior.sessionId, evidencia: 'duas consultas nativas concordam: mesma identidade, cwd e estado blocked',
|
|
460
|
+
decididoPor: 'núcleo ork', razao: 'despacho posterior da mesma fase verificado; não encerrar sessão trabalhando' });
|
|
461
|
+
if (!ctl.parar(antes))
|
|
462
|
+
throw new Error('runtime.unavailable: encerramento não confirmado; alerta preservado');
|
|
463
|
+
const depois = observar();
|
|
464
|
+
if (!terminou(depois) || depois && (depois.instancia !== antes.instancia || depois.cwd !== antes.cwd || depois.runtime !== runtime)) {
|
|
465
|
+
throw new Error('runtime.unavailable: observação posterior não confirma encerramento da mesma instância');
|
|
466
|
+
}
|
|
467
|
+
(0, ledger_1.registrar)(dir, id, 'session_superseded', { fase, sessionId, runtime, instancia: antes.instancia, cwd: antes.cwd,
|
|
468
|
+
confirmacao: depois?.estado ?? 'ausente',
|
|
469
|
+
superadaPor: posterior.sessionId, evidencia: depois ? `runtime confirmou ${depois.estado}` : 'consulta nativa bem-sucedida confirmou ausência após stop',
|
|
470
|
+
decididoPor: 'núcleo ork', razao: 'encerramento confirmado após comando oficial do runtime' });
|
|
471
|
+
return { ok: true, sessionId, repetida: false };
|
|
472
|
+
}
|
|
473
|
+
function superarSessao(raiz, id, sessionId, fase, runtime, controle) {
|
|
474
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => superarSobLock(raiz, id, sessionId, fase, runtime, controle));
|
|
475
|
+
}
|