@orkastery/cli 0.4.3 → 0.5.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/adapters/README.md +11 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +2 -2
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/master.md +11 -8
- package/adapters/claude-code/commands/ork.md +8 -0
- package/adapters/claude-code/commands/ship.md +2 -2
- package/adapters/codex/skills/ork/SKILL.md +12 -1
- package/adapters/hermes/README.md +3 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
- package/adapters/hermes/bin/ork-brain.sh +3 -0
- package/adapters/hermes/bin/ork-maestro.sh +4 -1
- package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
- package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
- package/adapters/hermes/hermes.plugin.json +2 -0
- package/adapters/hermes/hitl-ingress/__init__.py +36 -3
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
- package/adapters/openclaw/README.md +34 -2
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -20
- package/adapters/openclaw/openclaw.plugin.json +10 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -20
- package/assets/docs/padroes/documentacao-de-produto.md +2 -2
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs/produto/_modelo-feature.md +2 -0
- package/assets/orkmind_bridge.py +214 -1
- package/dist/adapters/claude-bg.js +6 -3
- package/dist/board.js +74 -0
- package/dist/branch-de-estado.js +5 -4
- package/dist/busca-semantica.js +161 -0
- package/dist/ci.js +141 -10
- package/dist/company-brain-cli.js +27 -2
- package/dist/company-brain-context.js +132 -0
- package/dist/company-brain-dossie.js +376 -0
- package/dist/company-brain-mcp.js +4 -1
- package/dist/conducao.js +71 -18
- package/dist/decisao-autonoma.js +42 -0
- package/dist/docs.js +64 -22
- package/dist/doctor.js +33 -0
- package/dist/entrega-pr.js +109 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +43 -10
- package/dist/fechamento.js +105 -0
- package/dist/forja.js +435 -0
- package/dist/gates.js +9 -1
- package/dist/hitl-classificacao.js +11 -1
- package/dist/hitl-contract.js +50 -7
- package/dist/hitl-curto.js +158 -0
- package/dist/hitl-gates.js +67 -1
- package/dist/hitl-lote.js +30 -15
- package/dist/hitl-presentation.js +10 -7
- package/dist/hitl-resumo.js +33 -12
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +86 -1
- package/dist/index.js +602 -38
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +13 -0
- package/dist/integracoes-locais.js +1 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +430 -0
- package/dist/intelligence-graph-contract.js +577 -0
- package/dist/intelligence-graph-extract-md.js +469 -0
- package/dist/intelligence-graph-extract-ts.js +810 -0
- package/dist/intelligence-graph-extract.js +369 -0
- package/dist/intelligence-graph-index.js +512 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +187 -0
- package/dist/leases.js +84 -0
- package/dist/ledger.js +14 -0
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +142 -5
- package/dist/master-batch.js +3 -2
- package/dist/master-digest.js +2 -2
- package/dist/master-nota.js +203 -0
- package/dist/master.js +4 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +4 -0
- package/dist/mcp-install.js +3 -1
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +84 -5
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +38 -2
- package/dist/onboarding.js +113 -7
- package/dist/orkmind.js +165 -9
- package/dist/phase.js +166 -13
- package/dist/policies.js +2 -0
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +5 -0
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +21 -0
- package/dist/retry.js +51 -8
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +4 -3
- package/eval/casos/orchestration-experience-pt-br.json +48 -0
- package/eval/casos/orchestration-experience.json +48 -0
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +581 -0
- package/schemas/graph-benchmark.v1.schema.json +2240 -0
- package/schemas/maestro-snapshot.schema.json +23 -0
- package/skills/README.md +7 -5
- package/skills/core/onboarding/SKILL.md +7 -0
- package/skills/core/orchestration-experience/SKILL.md +113 -0
- package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/go-implementation/SKILL.md +2 -0
|
@@ -0,0 +1,376 @@
|
|
|
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.ID_DE_DECISAO = exports.DOSSIE_SCHEMA = void 0;
|
|
37
|
+
exports.buildDossie = buildDossie;
|
|
38
|
+
/**
|
|
39
|
+
* K3.1: dossiê de decisão. Só leitura: compõe o que o núcleo já grava (o ledger da thread, o objetivo
|
|
40
|
+
* do K1 e o escopo vinculado) com o pacote de contexto do Brain, sem registrar nada. Decidir continua
|
|
41
|
+
* sendo `ork decisao registrar` (decisão delegada) ou a resposta do dono pelo ingresso autenticado.
|
|
42
|
+
*/
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const company_brain_client_1 = require("./company-brain-client");
|
|
46
|
+
const company_brain_contract_1 = require("./company-brain-contract");
|
|
47
|
+
const company_brain_context_1 = require("./company-brain-context");
|
|
48
|
+
const company_brain_source_1 = require("./company-brain-source");
|
|
49
|
+
const decisao_autonoma_1 = require("./decisao-autonoma");
|
|
50
|
+
const gates_1 = require("./gates");
|
|
51
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
52
|
+
const objective_1 = require("./objective");
|
|
53
|
+
const portfolio_1 = require("./portfolio");
|
|
54
|
+
const thread_1 = require("./thread");
|
|
55
|
+
exports.DOSSIE_SCHEMA = 'ork.dossie-de-decisao/v1';
|
|
56
|
+
/** O id que o dossiê aceita para uma decisão: o do pedido ou o que o Brain dá ao fato. */
|
|
57
|
+
exports.ID_DE_DECISAO = /^(?:[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}|fact-[a-f0-9]{64})$/;
|
|
58
|
+
/** Os mesmos números do pacote de contexto: lote da seleção e teto de `get` no caso misto. */
|
|
59
|
+
const LIMITE = 1000;
|
|
60
|
+
const LIMITE_DE_GET = 100;
|
|
61
|
+
const comparar = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
62
|
+
const falha = (state) => ['forbidden', 'unavailable', 'conflict'].includes(state);
|
|
63
|
+
const texto = (v) => typeof v === 'string' ? v : null;
|
|
64
|
+
/**
|
|
65
|
+
* D10: a mesma conferência do ramo `context` de `ork brain`, repetida aqui para não tocar aquele
|
|
66
|
+
* comando enquanto outra thread o muda. O escopo vale só se for desta thread e do formato do bind.
|
|
67
|
+
*/
|
|
68
|
+
function escopoVinculado(raiz, thread) {
|
|
69
|
+
const file = path.join((0, thread_1.dirThread)(raiz, thread), 'brain-scope.json');
|
|
70
|
+
if (!fs.existsSync(file))
|
|
71
|
+
return null;
|
|
72
|
+
let binding;
|
|
73
|
+
try {
|
|
74
|
+
binding = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw Error('brain.scope.invalid');
|
|
78
|
+
}
|
|
79
|
+
if (binding?.schema !== 'ork.brain-cycle-scope/v1' || binding.version !== 1 || binding.thread !== thread || typeof binding.projectId !== 'string' ||
|
|
80
|
+
!Array.isArray(binding.initiativeIds) || binding.initiativeIds.some((i) => typeof i !== 'string'))
|
|
81
|
+
throw Error('brain.scope.invalid');
|
|
82
|
+
return { projectId: binding.projectId, initiativeIds: binding.initiativeIds };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* D3: o objetivo é o ticket do K1 (`creationOrigin.ticketId`) e, sem ele, o objetivo cuja lista de
|
|
86
|
+
* threads contém esta; o projeto é o escopo vinculado (o que a captura grava no `cycle`) e, sem ele, o
|
|
87
|
+
* `portfolio` do objetivo. Fonte faltando ou discordando vira lacuna, nunca escolha silenciosa.
|
|
88
|
+
*/
|
|
89
|
+
function vinculoDaThread(raiz, t, lacuna) {
|
|
90
|
+
let achado = null, semObjetivo = true;
|
|
91
|
+
const ticket = t.creationOrigin?.ticketId;
|
|
92
|
+
try {
|
|
93
|
+
if (ticket) {
|
|
94
|
+
const o = (0, objective_1.readObjective)(raiz, ticket);
|
|
95
|
+
if (!o.threads.some(x => x.id === t.id)) {
|
|
96
|
+
lacuna(t.id, 'vinculo.divergente');
|
|
97
|
+
semObjetivo = false;
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
achado = { o, origem: 'ticket' };
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const comEla = (0, objective_1.listObjectives)(raiz).filter(o => o.threads.some(x => x.id === t.id));
|
|
104
|
+
if (comEla.length > 1) {
|
|
105
|
+
lacuna(t.id, 'vinculo.divergente');
|
|
106
|
+
semObjetivo = false;
|
|
107
|
+
}
|
|
108
|
+
else if (comEla.length === 1)
|
|
109
|
+
achado = { o: comEla[0], origem: 'lista-de-threads' };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
lacuna(ticket ?? t.id, 'objetivo.indisponivel');
|
|
114
|
+
semObjetivo = false;
|
|
115
|
+
}
|
|
116
|
+
if (!achado && semObjetivo)
|
|
117
|
+
lacuna(t.id, 'objetivo.ausente');
|
|
118
|
+
const escopo = escopoVinculado(raiz, t.id), doObjetivo = achado?.o.envelope.portfolio ?? null;
|
|
119
|
+
const ordenados = (ids) => [...new Set(ids)].sort(comparar);
|
|
120
|
+
let projeto = null;
|
|
121
|
+
if (escopo) {
|
|
122
|
+
const entidade = (0, portfolio_1.findEntity)(raiz, escopo.projectId);
|
|
123
|
+
projeto = { productId: entidade?.kind === 'project' ? entidade.productId : null, projectId: escopo.projectId,
|
|
124
|
+
initiativeIds: ordenados(escopo.initiativeIds), origem: 'escopo-vinculado' };
|
|
125
|
+
if (doObjetivo && (doObjetivo.projectId !== projeto.projectId ||
|
|
126
|
+
JSON.stringify(ordenados(doObjetivo.initiativeIds)) !== JSON.stringify(projeto.initiativeIds)))
|
|
127
|
+
lacuna(t.id, 'vinculo.divergente');
|
|
128
|
+
}
|
|
129
|
+
else if (doObjetivo) {
|
|
130
|
+
projeto = { productId: doObjetivo.productId, projectId: doObjetivo.projectId, initiativeIds: ordenados(doObjetivo.initiativeIds), origem: 'objetivo' };
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
lacuna(t.id, 'projeto.ausente');
|
|
134
|
+
// Título e estado ficam fora do envelope com hash: o que faltar sai vazio, nunca `undefined` no digest.
|
|
135
|
+
const objetivo = achado ? { id: achado.o.id, titulo: texto(achado.o.title) ?? '', estado: texto(achado.o.status) ?? '',
|
|
136
|
+
envelopeHash: achado.o.envelope.hash, origem: achado.origem } : null;
|
|
137
|
+
return { objetivo, projeto, brain: { thread_id: t.id, objective_id: objetivo?.id ?? null, project_id: projeto?.projectId ?? null,
|
|
138
|
+
initiative_ids: projeto?.initiativeIds ?? [] } };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Uma seleção por lote de ids `fact-…`; retido chega sem id, e só o caso misto pede `get`, como no contexto.
|
|
142
|
+
* Seleção que não responde `ok` nem `empty` fecha o dossiê: sem estado conhecido, nada local vira conteúdo.
|
|
143
|
+
*/
|
|
144
|
+
function consultarFatos(transport, tenant, ids) {
|
|
145
|
+
const ok = new Map(), retidos = new Set();
|
|
146
|
+
let gets = 0;
|
|
147
|
+
for (let inicio = 0; inicio < ids.length; inicio += LIMITE) {
|
|
148
|
+
const lote = ids.slice(inicio, inicio + LIMITE), doLote = new Set(lote);
|
|
149
|
+
const r = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'query', payload: (0, company_brain_contract_1.validateContract)({ schema: 'orkmind.company-brain-selection/v1',
|
|
150
|
+
tenant_id: tenant, facets: { ids: lote, kinds: [], workspace_ids: [], source_instances: [] }, mode: 'selection', limit: lote.length, offset: 0 }) });
|
|
151
|
+
if (r.state !== 'ok' && r.state !== 'empty')
|
|
152
|
+
return r;
|
|
153
|
+
const itens = Array.isArray(r.items) ? r.items : [];
|
|
154
|
+
for (const item of itens)
|
|
155
|
+
if (item?.state === 'ok' && doLote.has(item.entity?.id))
|
|
156
|
+
ok.set(item.entity.id, item.entity);
|
|
157
|
+
const faltantes = lote.filter(id => !ok.has(id)), retidosNoLote = itens.filter((i) => i?.state === 'withheld').length;
|
|
158
|
+
if (retidosNoLote && retidosNoLote === faltantes.length)
|
|
159
|
+
faltantes.forEach(id => retidos.add(id));
|
|
160
|
+
else if (retidosNoLote)
|
|
161
|
+
for (const id of faltantes) {
|
|
162
|
+
if (++gets > LIMITE_DE_GET)
|
|
163
|
+
return { schema: company_brain_client_1.BRAIN_API, state: 'unavailable', error: 'brain.dossie.get-limit' };
|
|
164
|
+
const g = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'get', payload: { tenant_id: tenant, id } });
|
|
165
|
+
if (falha(g.state))
|
|
166
|
+
return g;
|
|
167
|
+
if (g.state === 'withheld')
|
|
168
|
+
retidos.add(id);
|
|
169
|
+
else if (g.state === 'ok' && g.entity?.id === id)
|
|
170
|
+
ok.set(id, g.entity);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { ok, retidos };
|
|
174
|
+
}
|
|
175
|
+
const TIPOS_DO_DOSSIE = new Set(['autonomous_decision', 'hitl_requested', 'human_gate', 'human_decision']);
|
|
176
|
+
/**
|
|
177
|
+
* P2: o ledger é lido pela `readCycle`, a mesma leitura da captura. Uma linha que o contrato do Brain não
|
|
178
|
+
* representa (horário fora do formato, em ledgers antigos) derruba a leitura inteira; então a faixa que
|
|
179
|
+
* recusa é partida ao meio até isolar a linha, que fica sem id no Brain. Qualquer outro erro (linha
|
|
180
|
+
* corrompida, de outra thread) continua recusando o dossiê, como recusa a captura.
|
|
181
|
+
*/
|
|
182
|
+
function linhasDoLedger(bytes, scope) {
|
|
183
|
+
const brutas = bytes.subarray(0, bytes.lastIndexOf(10) + 1).toString('utf8').split('\n').slice(0, -1);
|
|
184
|
+
const inicios = [0];
|
|
185
|
+
for (const raw of brutas)
|
|
186
|
+
inicios.push(inicios[inicios.length - 1] + Buffer.byteLength(raw) + 1);
|
|
187
|
+
const linhas = [];
|
|
188
|
+
const faixa = (de, ate) => {
|
|
189
|
+
try {
|
|
190
|
+
(0, company_brain_source_1.readCycle)(bytes.subarray(0, inicios[ate]), scope, inicios[de]).records
|
|
191
|
+
.forEach((r, i) => linhas.push({ n: de + i + 1, e: JSON.parse(brutas[de + i]), evento: r.event }));
|
|
192
|
+
}
|
|
193
|
+
catch (erro) {
|
|
194
|
+
if (erro.message !== 'brain.contract.invalid')
|
|
195
|
+
throw erro;
|
|
196
|
+
if (ate - de === 1) {
|
|
197
|
+
linhas.push({ n: de + 1, e: JSON.parse(brutas[de]), evento: null });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const meio = de + Math.floor((ate - de) / 2);
|
|
201
|
+
faixa(de, meio);
|
|
202
|
+
faixa(meio, ate);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
if (brutas.length)
|
|
206
|
+
faixa(0, brutas.length);
|
|
207
|
+
return linhas.filter(l => TIPOS_DO_DOSSIE.has(l.e.tipo));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Monta o dossiê de uma thread ou de uma decisão dela. A fonte canônica da decisão é o ledger; o Brain
|
|
211
|
+
* é projeção, e a diferença entre os dois sai como frescor e lacuna. Item sem citação, resposta sem
|
|
212
|
+
* recibo conferido e decisão fora do contrato viram lacuna, nunca conteúdo.
|
|
213
|
+
*/
|
|
214
|
+
function buildDossie(c, thread, decisao, transport) {
|
|
215
|
+
// Filtro vazio é erro, não a thread inteira: quem pede uma decisão recebe essa ou a recusa.
|
|
216
|
+
if (decisao !== undefined && !exports.ID_DE_DECISAO.test(decisao))
|
|
217
|
+
throw Error('brain.dossie.decisao-invalida');
|
|
218
|
+
const filtro = decisao ?? null;
|
|
219
|
+
const t = (0, thread_1.lerThread)(c.raiz, thread), tenant = c.manifesto.memory.tenant;
|
|
220
|
+
const lacunas = [];
|
|
221
|
+
const lacuna = (id, codigo) => { lacunas.push({ id, codigo }); };
|
|
222
|
+
const vinculo = vinculoDaThread(c.raiz, t, lacuna);
|
|
223
|
+
const base = { schema: exports.DOSSIE_SCHEMA, tenant, contrato: company_brain_contract_1.CONTRACT_HASH, thread: t.id, decisao: filtro };
|
|
224
|
+
const encerrar = (r) => ({ ...base, state: r.state, ...(r.error ? { error: r.error } : {}),
|
|
225
|
+
vinculo, contexto: null, decisoes: [], lacunas: [], digest: null, consultadoEm: new Date().toISOString() });
|
|
226
|
+
const arquivo = path.join((0, thread_1.dirThread)(c.raiz, t.id), 'ledger.jsonl');
|
|
227
|
+
const bytes = fs.existsSync(arquivo) ? (0, company_brain_source_1.readSourceFile)(arquivo) : Buffer.alloc(0);
|
|
228
|
+
const linhas = linhasDoLedger(bytes, { tenant, instance: c.manifesto.project.name, thread: t.id, aclRef: 'ork-factory' });
|
|
229
|
+
// Cada linha é classificada uma vez, pelo validador do contrato; o que não valida é formato anterior.
|
|
230
|
+
const classe = new Map(), respostas = new Map(), revertidaPor = new Map();
|
|
231
|
+
const semIngresso = [];
|
|
232
|
+
const pedidoDe = (l) => l.e.pedido;
|
|
233
|
+
for (const l of linhas) {
|
|
234
|
+
const p = l.e.pedido;
|
|
235
|
+
if (l.e.tipo === 'autonomous_decision') {
|
|
236
|
+
let decidido = false;
|
|
237
|
+
try {
|
|
238
|
+
(0, hitl_contract_1.validarPedidoHitlV2)(p);
|
|
239
|
+
decidido = p.classe === 'decidido';
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
decidido = false;
|
|
243
|
+
}
|
|
244
|
+
classe.set(l, decidido ? 'decidido' : 'legado');
|
|
245
|
+
if (decidido && typeof l.e.reverte === 'string')
|
|
246
|
+
revertidaPor.set(l.e.reverte, l);
|
|
247
|
+
}
|
|
248
|
+
else if (l.e.tipo === 'hitl_requested') {
|
|
249
|
+
try {
|
|
250
|
+
(0, hitl_contract_1.validarPedidoHitl)(p);
|
|
251
|
+
if ((0, hitl_contract_1.alvoDoPedido)(p)?.tipo === 'gate')
|
|
252
|
+
classe.set(l, 'pergunta');
|
|
253
|
+
}
|
|
254
|
+
catch { /* fora do contrato: não é pergunta */ }
|
|
255
|
+
}
|
|
256
|
+
else if (l.e.tipo === 'human_gate' && typeof l.e.pedidoId === 'string')
|
|
257
|
+
respostas.set(l.e.pedidoId, [...(respostas.get(l.e.pedidoId) ?? []), l]);
|
|
258
|
+
else if (l.e.tipo === 'human_decision')
|
|
259
|
+
semIngresso.push(l);
|
|
260
|
+
}
|
|
261
|
+
const candidatas = [...classe.keys()];
|
|
262
|
+
const referencia = (l) => `threads/${t.id}/ledger.jsonl#L${l.n}`;
|
|
263
|
+
const idDe = (l) => classe.get(l) !== 'legado' ? String(pedidoDe(l).id) : l.evento?.aggregate_id ?? referencia(l);
|
|
264
|
+
const escolhidas = filtro === null ? candidatas : candidatas.filter(l => idDe(l) === filtro || l.evento?.aggregate_id === filtro);
|
|
265
|
+
if (filtro !== null && !escolhidas.length)
|
|
266
|
+
lacuna(filtro, 'decisao.desconhecida');
|
|
267
|
+
// D8: `human_decision` é o relato de alguém sobre o dono, sem recibo: nunca vira decisão do dono.
|
|
268
|
+
if (filtro === null)
|
|
269
|
+
for (const l of semIngresso)
|
|
270
|
+
lacuna(referencia(l), 'decisao.humana-sem-ingresso');
|
|
271
|
+
// D4: a resposta só entra com o recibo reconferido pelas validadoras do núcleo, qualquer que seja o veredito.
|
|
272
|
+
const provadas = new Map();
|
|
273
|
+
for (const l of escolhidas)
|
|
274
|
+
if (classe.get(l) === 'pergunta') {
|
|
275
|
+
const gates = respostas.get(idDe(l)) ?? [];
|
|
276
|
+
if (gates.length === 1 && gates[0].evento && (0, gates_1.reciboHumanoConfere)(c.raiz, t.id, gates[0].e))
|
|
277
|
+
provadas.set(idDe(l), gates[0]);
|
|
278
|
+
}
|
|
279
|
+
let contexto = null;
|
|
280
|
+
if (vinculo.projeto) {
|
|
281
|
+
contexto = (0, company_brain_context_1.buildContext)(c, [vinculo.projeto.projectId, ...vinculo.projeto.initiativeIds], transport, t.id);
|
|
282
|
+
if (falha(contexto.state))
|
|
283
|
+
return encerrar(contexto);
|
|
284
|
+
}
|
|
285
|
+
// A decisão que desfaz outra também é conferida: a relação é conteúdo dela, e some se ela estiver retida.
|
|
286
|
+
const reversoras = escolhidas.flatMap(l => classe.get(l) === 'decidido' ? [revertidaPor.get(idDe(l))].filter((x) => !!x) : []);
|
|
287
|
+
const fatos = [...new Set([...escolhidas, ...provadas.values(), ...reversoras].flatMap(l => l.evento ? [l.evento.aggregate_id] : []))].sort(comparar);
|
|
288
|
+
let noBrain = { ok: new Map(), retidos: new Set() };
|
|
289
|
+
if (fatos.length) {
|
|
290
|
+
const r = consultarFatos(transport, tenant, fatos);
|
|
291
|
+
if ('state' in r)
|
|
292
|
+
return encerrar(r);
|
|
293
|
+
noBrain = r;
|
|
294
|
+
}
|
|
295
|
+
const ids = (evento) => ({ assertion_id: evento.aggregate_id, event_id: evento.id, source_event_id: evento.source_event_id });
|
|
296
|
+
const fato = (evento) => {
|
|
297
|
+
const id = evento.aggregate_id;
|
|
298
|
+
if (noBrain.retidos.has(id)) {
|
|
299
|
+
lacuna(id, 'brain.retido');
|
|
300
|
+
return { id, frescor: 'retido' };
|
|
301
|
+
}
|
|
302
|
+
const { instance, source_ref, source_hash, source_version, location } = evento.source, b = noBrain.ok.get(id);
|
|
303
|
+
const frescor = !b ? 'ausente-no-brain' : b.source?.source_hash === source_hash && b.source?.source_ref === source_ref ? 'confere' : 'divergente';
|
|
304
|
+
if (frescor === 'ausente-no-brain')
|
|
305
|
+
lacuna(id, 'brain.ausente');
|
|
306
|
+
if (frescor === 'divergente')
|
|
307
|
+
lacuna(id, 'fonte.divergente');
|
|
308
|
+
return { brain: ids(evento), citacao: { instance, source_ref, source_hash, source_version, location }, frescor };
|
|
309
|
+
};
|
|
310
|
+
const visivel = (l) => !!l?.evento && !noBrain.retidos.has(l.evento.aggregate_id);
|
|
311
|
+
const decisoes = [];
|
|
312
|
+
for (const l of escolhidas) {
|
|
313
|
+
// Sem id no Brain não há citação: a decisão vira lacuna, nunca conteúdo.
|
|
314
|
+
if (!l.evento) {
|
|
315
|
+
lacuna(idDe(l), 'citacao.incompleta');
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const id = idDe(l), f = fato(l.evento), e = l.e, tipo = classe.get(l);
|
|
319
|
+
// D6: retido pelo Brain não mostra nem o conteúdo local (BR-024-03).
|
|
320
|
+
if (f.frescor === 'retido') {
|
|
321
|
+
decisoes.push(f);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const comum = { id, fase: texto(e.fase), em: e.ts, ...f };
|
|
325
|
+
if (tipo === 'decidido') {
|
|
326
|
+
const p = pedidoDe(l);
|
|
327
|
+
decisoes.push({ ...comum, classe: tipo, contrato: p.contrato, autoria: 'autonoma', decidido: p.decidido, porque: p.porque, comoMudar: p.comoMudar,
|
|
328
|
+
custoDeReverter: { agora: p.custoDeReverter.agora, depois: p.custoDeReverter.depois }, criterio: { tipo: p.criterio.tipo, referencia: p.criterio.referencia },
|
|
329
|
+
quemDecidiu: String(e.quemDecidiu ?? ''), evidencia: String(e.evidencia ?? ''), razao: String(e.razao ?? ''),
|
|
330
|
+
reverte: texto(e.reverte), revertidaPor: visivel(revertidaPor.get(p.id)) ? String(pedidoDe(revertidaPor.get(p.id)).id) : null });
|
|
331
|
+
// D-G3: o contrato do `decidido` não guarda alternativas; o `comoMudar` é o caminho de volta.
|
|
332
|
+
lacuna(id, 'alternativas.nao-registradas');
|
|
333
|
+
}
|
|
334
|
+
else if (tipo === 'pergunta') {
|
|
335
|
+
const p = pedidoDe(l);
|
|
336
|
+
const alternativas = p.contrato === 'ork.hitl/v2'
|
|
337
|
+
? p.alternativas.map((a, i) => ({ numero: i + 1, letra: a.letra, texto: a.texto, acao: a.acao,
|
|
338
|
+
consequencia: a.consequencia, recomendada: a.recomendada === true, porque: a.porque ?? null }))
|
|
339
|
+
: p.opcoes.map(o => ({ numero: o.numero, letra: null, texto: o.texto, acao: o.acao, consequencia: null, recomendada: false, porque: null }));
|
|
340
|
+
const provada = provadas.get(id), gates = respostas.get(id) ?? [];
|
|
341
|
+
let resposta = null, estado = 'aguardando';
|
|
342
|
+
if (provada) {
|
|
343
|
+
const r = fato(provada.evento), g = provada.e, escolha = alternativas.find(a => a.numero === g.opcao);
|
|
344
|
+
// Resposta retida: nem a escolha nem a autoria saem da linha local.
|
|
345
|
+
estado = r.frescor === 'retido' ? 'retida' : 'decidida';
|
|
346
|
+
resposta = r.frescor === 'retido' ? r : { ...r, opcao: Number.isInteger(g.opcao) ? g.opcao : null, letra: escolha?.letra ?? null,
|
|
347
|
+
texto: escolha?.texto ?? null, veredito: texto(g.estado), quem: String(g.autorizadoPor ?? ''), origem: String(g.origem ?? ''),
|
|
348
|
+
canal: texto(g.canal), recebidoEm: texto(g.recebidoEm), recibo: texto(g.recibo), evidencia: texto(g.evidencia), evidenciaSha256: texto(g.evidenciaSha256) };
|
|
349
|
+
}
|
|
350
|
+
else if (gates.length) {
|
|
351
|
+
estado = 'sem-prova';
|
|
352
|
+
lacuna(id, 'resposta.sem-prova');
|
|
353
|
+
}
|
|
354
|
+
else
|
|
355
|
+
lacuna(id, 'resposta.pendente');
|
|
356
|
+
// O prazo vai como está no pedido; vencido ou não é leitura de quem consulta, e o digest não depende do relógio.
|
|
357
|
+
decisoes.push({ ...comum, classe: tipo, contrato: p.contrato, autoria: estado === 'decidida' ? 'dono' : null, pergunta: String(p.pergunta),
|
|
358
|
+
sobre: (0, hitl_contract_1.alvoDoPedido)(p)?.tipo === 'gate' ? String((0, hitl_contract_1.alvoDoPedido)(p).sobre) : '', alternativas,
|
|
359
|
+
recomendacao: p.contrato === 'ork.hitl/v2' ? null : texto(p.recomendacao), prazo: texto(p.prazo),
|
|
360
|
+
estado, resposta });
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
const rastro = (0, decisao_autonoma_1.rastroDaDecisao)(e);
|
|
364
|
+
decisoes.push({ ...comum, classe: tipo, contrato: 'legado', autoria: 'autonoma', decidido: texto(e.decisao) ?? '',
|
|
365
|
+
quemDecidiu: rastro.quemDecidiu ?? null, evidencia: rastro.evidencia ?? null, razao: rastro.razao ?? null, rastroCompleto: rastro.completa });
|
|
366
|
+
lacuna(id, 'decisao.fora-do-contrato');
|
|
367
|
+
lacuna(id, 'alternativas.nao-registradas');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const unicas = [...new Map(lacunas.map(x => [`${x.id} ${x.codigo}`, x])).values()]
|
|
371
|
+
.sort((a, b) => comparar(a.id, b.id) || comparar(a.codigo, b.codigo));
|
|
372
|
+
// D7: o digest cobre o digest do pacote de contexto, não o pacote inteiro; o horário fica fora.
|
|
373
|
+
const corpo = { ...base, state: (decisoes.length ? 'ok' : 'empty'), vinculo, contexto: contexto?.digest ?? null,
|
|
374
|
+
decisoes, lacunas: unicas };
|
|
375
|
+
return { ...corpo, contexto, digest: (0, company_brain_contract_1.digest)(corpo), consultadoEm: new Date().toISOString() };
|
|
376
|
+
}
|
|
@@ -4,7 +4,8 @@ exports.BRAIN_WRITE_TOOLS = exports.BRAIN_READ_TOOLS = void 0;
|
|
|
4
4
|
exports.registerBrainTools = registerBrainTools;
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const company_brain_cli_1 = require("./company-brain-cli");
|
|
7
|
-
|
|
7
|
+
const company_brain_dossie_1 = require("./company-brain-dossie");
|
|
8
|
+
exports.BRAIN_READ_TOOLS = ['ork_brain_status', 'ork_brain_inventory', 'ork_brain_get', 'ork_brain_query', 'ork_brain_receipts', 'ork_brain_context', 'ork_brain_dossie'];
|
|
8
9
|
exports.BRAIN_WRITE_TOOLS = ['ork_brain_sync', 'ork_brain_apply', 'ork_brain_rollback', 'ork_brain_bind'];
|
|
9
10
|
/** Installation supplies exact grants; presence of a tool is never activation or human identity. */
|
|
10
11
|
function registerBrainTools(register, load, checkThread, writeGrants = []) {
|
|
@@ -13,6 +14,8 @@ function registerBrainTools(register, load, checkThread, writeGrants = []) {
|
|
|
13
14
|
const id = zod_1.z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/), base = zod_1.z.object({ threadId: id }).strict();
|
|
14
15
|
const definitions = [['status', base], ['inventory', base], ['get', base.extend({ id })], ['receipts', base.extend({ id })],
|
|
15
16
|
['query', base.extend({ ids: zod_1.z.array(id).max(1000).optional(), kinds: zod_1.z.array(zod_1.z.enum(['prod', 'proj', 'init', 'assertion'])).optional(), limit: zod_1.z.number().int().min(1).max(1000).optional() })],
|
|
17
|
+
['context', base.extend({ ids: zod_1.z.array(id).min(1).max(1000).optional() })],
|
|
18
|
+
['dossie', base.extend({ decisao: zod_1.z.string().regex(company_brain_dossie_1.ID_DE_DECISAO).optional() })],
|
|
16
19
|
['sync', base], ['apply', base.extend({ plan: zod_1.z.string().min(1).max(512), expectedSha256: zod_1.z.string().regex(/^[a-f0-9]{64}$/) })],
|
|
17
20
|
['rollback', base.extend({ batch: id })], ['bind', base.extend({ project: id, initiatives: zod_1.z.array(id).min(1).max(1000) })]];
|
|
18
21
|
for (const [operation, inputSchema] of definitions) {
|
package/dist/conducao.js
CHANGED
|
@@ -338,6 +338,18 @@ function vidaDoProcesso(raiz, threadId, lease) {
|
|
|
338
338
|
fs.closeSync(fd);
|
|
339
339
|
return 'morto';
|
|
340
340
|
}
|
|
341
|
+
/** Fecha a tomada uma vez so (RM-037, CHECK 4): liberar e devolver podem ser chamados mais de uma vez. */
|
|
342
|
+
function encerrar(k, estado) {
|
|
343
|
+
if (estado.encerrada)
|
|
344
|
+
return;
|
|
345
|
+
estado.encerrada = true;
|
|
346
|
+
if (emCurso.get(k) === estado)
|
|
347
|
+
emCurso.delete(k);
|
|
348
|
+
try {
|
|
349
|
+
fs.closeSync(estado.fd);
|
|
350
|
+
}
|
|
351
|
+
catch { /* ja fechado */ }
|
|
352
|
+
}
|
|
341
353
|
/** Conducoes que ESTE processo segura, pela chave canonica: a reentrada no mesmo processo. */
|
|
342
354
|
const emCurso = new Map();
|
|
343
355
|
function chave(raiz, threadId) {
|
|
@@ -432,7 +444,7 @@ function liberarComProva(raiz, threadId, lease, tipo, dados) {
|
|
|
432
444
|
* estava livre, entao ele morreu). Dono sessao vale ate a prova de fim. Reserva vale no prazo, e para
|
|
433
445
|
* o mesmo canal vira a conducao dele.
|
|
434
446
|
*/
|
|
435
|
-
function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true }) {
|
|
447
|
+
function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true }, saida = {}) {
|
|
436
448
|
const lease = (0, leases_1.lerLease)(raiz, nomeDaConducao(threadId));
|
|
437
449
|
if (!lease)
|
|
438
450
|
return null;
|
|
@@ -460,6 +472,7 @@ function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true })
|
|
|
460
472
|
return null;
|
|
461
473
|
}
|
|
462
474
|
if (opcoes.usarReserva && atual.canal === pedido.canal) {
|
|
475
|
+
saida.substituido = lease;
|
|
463
476
|
liberarComProva(raiz, threadId, lease, ledger_1.TIPOS_DE_EVENTO.conducaoLiberada, { motivo: 'reserva-usada', prova: `pedido do mesmo canal ${pedido.canal} de quem assumiu` });
|
|
464
477
|
return null;
|
|
465
478
|
}
|
|
@@ -477,6 +490,7 @@ function avaliarComTrava(raiz, threadId, pedido, opcoes = { usarReserva: true })
|
|
|
477
490
|
// A sucessora (fluxo de superacao das sessoes HITL): a sessao `blocked` espera alguem e nao executa;
|
|
478
491
|
// o despacho seguinte da MESMA fase a sucede, e a superacao a encerra pelo controle do runtime.
|
|
479
492
|
if (despacho && mesmaFase && !mesmoPedido && estadoNoRuntime === 'blocked') {
|
|
493
|
+
saida.substituido = lease;
|
|
480
494
|
liberarComProva(raiz, threadId, lease, ledger_1.TIPOS_DE_EVENTO.conducaoLiberada, {
|
|
481
495
|
motivo: 'sessao-bloqueada-sucedida',
|
|
482
496
|
prova: `runtime ${atual.dono.tipo === 'sessao' ? atual.dono.runtime : '?'}: estado blocked; o despacho seguinte da fase ${atual.fase} a sucede`,
|
|
@@ -497,7 +511,8 @@ function tomarConducao(raiz, threadId, pedido) {
|
|
|
497
511
|
const propria = emCurso.get(k);
|
|
498
512
|
if (propria && !propria.convertida) {
|
|
499
513
|
propria.profundidade++;
|
|
500
|
-
|
|
514
|
+
// R5-S2 do CHECK 5: a reentrada renova o lease de quem a segura (a baseline dentro da tomada retida).
|
|
515
|
+
return reentrada(propria.identidade, () => { propria.profundidade--; }, (prazo, agoraMs = Date.now()) => renovar(raiz, threadId, propria, prazo, agoraMs));
|
|
501
516
|
}
|
|
502
517
|
const existente = (0, leases_1.lerLease)(raiz, nomeDaConducao(threadId));
|
|
503
518
|
if (pedido.identidade && existente?.conducao?.identidade === pedido.identidade) {
|
|
@@ -521,7 +536,8 @@ function tomarConducao(raiz, threadId, pedido) {
|
|
|
521
536
|
return { ok: false, idempotente: false, atual };
|
|
522
537
|
}
|
|
523
538
|
try {
|
|
524
|
-
const
|
|
539
|
+
const saida = {};
|
|
540
|
+
const ocupada = avaliarComTrava(raiz, threadId, pedido, undefined, saida);
|
|
525
541
|
if (ocupada) {
|
|
526
542
|
fs.closeSync(fd);
|
|
527
543
|
return ocupada;
|
|
@@ -537,7 +553,8 @@ function tomarConducao(raiz, threadId, pedido) {
|
|
|
537
553
|
conducao: dadosDoPedido(pedido, identidade, donoProcesso()),
|
|
538
554
|
};
|
|
539
555
|
(0, leases_1.regravarLease)(raiz, lease);
|
|
540
|
-
const estado = { identidade, profundidade: 0, lease, fd, convertida: false }
|
|
556
|
+
const estado = { identidade, profundidade: 0, lease, fd, convertida: false, ...(saida.substituido ? { substituido: saida.substituido } : {}),
|
|
557
|
+
...(pedido.consultarSessao ? { consultarSessao: pedido.consultarSessao } : {}) };
|
|
541
558
|
emCurso.set(k, estado);
|
|
542
559
|
return {
|
|
543
560
|
ok: true,
|
|
@@ -546,6 +563,8 @@ function tomarConducao(raiz, threadId, pedido) {
|
|
|
546
563
|
renovar: (prazoDoProximoMs, agoraMs = Date.now()) => renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs),
|
|
547
564
|
converterEmSessao: (dados) => converter(raiz, k, estado, dados),
|
|
548
565
|
liberar: () => liberarPropria(raiz, k, estado),
|
|
566
|
+
devolver: () => devolverPropria(raiz, threadId, k, estado),
|
|
567
|
+
sucessaoAindaVale: () => sucessaoAindaVale(raiz, threadId, estado),
|
|
549
568
|
};
|
|
550
569
|
}
|
|
551
570
|
catch (e) {
|
|
@@ -556,20 +575,42 @@ function tomarConducao(raiz, threadId, pedido) {
|
|
|
556
575
|
throw e;
|
|
557
576
|
}
|
|
558
577
|
}
|
|
559
|
-
function reentrada(identidade, sair) {
|
|
578
|
+
function reentrada(identidade, sair, renovarExterno = () => undefined) {
|
|
560
579
|
let saiu = false;
|
|
561
580
|
return {
|
|
562
581
|
ok: true, reentrada: true, identidade,
|
|
563
|
-
renovar:
|
|
582
|
+
renovar: renovarExterno,
|
|
564
583
|
converterEmSessao: () => undefined,
|
|
565
584
|
liberar: () => { if (!saiu) {
|
|
566
585
|
saiu = true;
|
|
567
586
|
sair();
|
|
568
587
|
} },
|
|
588
|
+
devolver: () => { if (!saiu) {
|
|
589
|
+
saiu = true;
|
|
590
|
+
sair();
|
|
591
|
+
} },
|
|
592
|
+
sucessaoAindaVale: () => true,
|
|
569
593
|
};
|
|
570
594
|
}
|
|
595
|
+
/** Ver `ConducaoTomada.sucessaoAindaVale`. Sessao sucedida que voltou a trabalhar derruba a sucessao. */
|
|
596
|
+
function sucessaoAindaVale(raiz, threadId, estado) {
|
|
597
|
+
// S2 do CHECK 6: tomada ja encerrada nao converte mais em sessao; despachar com ela deixaria a sessao sem lease.
|
|
598
|
+
if (estado.encerrada)
|
|
599
|
+
return false;
|
|
600
|
+
const dono = estado.substituido?.conducao?.dono;
|
|
601
|
+
if (!dono || dono.tipo !== 'sessao' || estado.convertida)
|
|
602
|
+
return true;
|
|
603
|
+
const r = (estado.consultarSessao ?? consultaNativa)(dono, raiz, threadId);
|
|
604
|
+
// Sem resposta do runtime nao ha prova de que ela segue parada: a sucessao nao vale.
|
|
605
|
+
if (!r.ok)
|
|
606
|
+
return false;
|
|
607
|
+
// S1 do CHECK 6: a sessao que sumiu do runtime, sem perfil, acabou; a mesma prova de `provaDeFimDaSessao`.
|
|
608
|
+
if (r.estado === null)
|
|
609
|
+
return !dono.perfil;
|
|
610
|
+
return r.estado === 'blocked' || TERMINAIS.includes(r.estado);
|
|
611
|
+
}
|
|
571
612
|
function renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs) {
|
|
572
|
-
if (estado.convertida)
|
|
613
|
+
if (estado.convertida || estado.encerrada)
|
|
573
614
|
return;
|
|
574
615
|
const falta = Date.parse(estado.lease.expiraEm) - agoraMs;
|
|
575
616
|
if (falta >= prazoDoProximoMs + exports.MARGEM_DO_PRAZO_MS)
|
|
@@ -585,7 +626,7 @@ function renovar(raiz, threadId, estado, prazoDoProximoMs, agoraMs) {
|
|
|
585
626
|
});
|
|
586
627
|
}
|
|
587
628
|
function converter(raiz, k, estado, dados) {
|
|
588
|
-
if (estado.convertida)
|
|
629
|
+
if (estado.convertida || estado.encerrada)
|
|
589
630
|
return;
|
|
590
631
|
const c = estado.lease.conducao;
|
|
591
632
|
estado.lease = {
|
|
@@ -596,14 +637,30 @@ function converter(raiz, k, estado, dados) {
|
|
|
596
637
|
};
|
|
597
638
|
(0, leases_1.regravarLease)(raiz, estado.lease);
|
|
598
639
|
estado.convertida = true;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
640
|
+
encerrar(k, estado);
|
|
641
|
+
}
|
|
642
|
+
function devolverPropria(raiz, threadId, k, estado) {
|
|
643
|
+
const anterior = estado.substituido;
|
|
644
|
+
if (estado.convertida || estado.encerrada || estado.profundidade > 0 || !anterior) {
|
|
645
|
+
liberarPropria(raiz, k, estado);
|
|
646
|
+
return;
|
|
602
647
|
}
|
|
603
|
-
|
|
648
|
+
const atual = (0, leases_1.lerLease)(raiz, estado.lease.nome);
|
|
649
|
+
// So devolve por cima do proprio lease: outro dono que tenha chegado depois nunca e sobrescrito.
|
|
650
|
+
if (atual?.conducao?.identidade === estado.identidade && atual.conducao.dono.tipo === 'processo') {
|
|
651
|
+
(0, leases_1.regravarLease)(raiz, anterior);
|
|
652
|
+
const devolvida = (0, conducao_texto_1.conducaoDoLease)(anterior);
|
|
653
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.conducaoDevolvida, {
|
|
654
|
+
lease: anterior.nome,
|
|
655
|
+
devolvida: devolvida ? { canal: devolvida.canal, sessao: devolvida.sessao, fase: devolvida.fase, desde: devolvida.desde,
|
|
656
|
+
operacao: devolvida.operacao, dono: devolvida.dono } : null,
|
|
657
|
+
razao: 'o pedido foi recusado antes de virar sessao: o lease que a tomada consumiu volta a quem o tinha',
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
encerrar(k, estado);
|
|
604
661
|
}
|
|
605
662
|
function liberarPropria(raiz, k, estado) {
|
|
606
|
-
if (estado.convertida)
|
|
663
|
+
if (estado.convertida || estado.encerrada)
|
|
607
664
|
return;
|
|
608
665
|
if (estado.profundidade > 0) {
|
|
609
666
|
estado.profundidade--;
|
|
@@ -616,11 +673,7 @@ function liberarPropria(raiz, k, estado) {
|
|
|
616
673
|
}
|
|
617
674
|
catch { /* ja saiu */ }
|
|
618
675
|
}
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
fs.closeSync(estado.fd);
|
|
622
|
-
}
|
|
623
|
-
catch { /* ja fechado */ }
|
|
676
|
+
encerrar(k, estado);
|
|
624
677
|
}
|
|
625
678
|
/**
|
|
626
679
|
* Registra a conducao da sessao de um despacho que nao tomou o lease antes (chamada direta de
|
package/dist/decisao-autonoma.js
CHANGED
|
@@ -39,7 +39,43 @@ const hitl_gates_1 = require("./hitl-gates");
|
|
|
39
39
|
const ledger_1 = require("./ledger");
|
|
40
40
|
const thread_1 = require("./thread");
|
|
41
41
|
const modos_1 = require("./modos");
|
|
42
|
+
const conducao_1 = require("./conducao");
|
|
42
43
|
exports.CONTRATO_DECISAO_AUTONOMA = 'ork.decisao-autonoma/v1';
|
|
44
|
+
/**
|
|
45
|
+
* A sessao de OUTRA thread que registra aqui tambem deixa rastro (S-3 do CHECK 3). So o par que o ledger
|
|
46
|
+
* daquela thread confirma (S-b do CHECK 4): o par vazado do daemon do `claude --bg` nao vira rastro falso.
|
|
47
|
+
*/
|
|
48
|
+
function despachoDeOutraThread(raiz, threadId) {
|
|
49
|
+
const thread = (process.env[conducao_1.ENV_THREAD_DO_DESPACHO] ?? '').trim(), id = (process.env[conducao_1.ENV_IDENTIDADE_DE_DESPACHO] ?? '').trim();
|
|
50
|
+
if (!thread || thread === threadId || !/^[a-z0-9]{1,3}-[a-z0-9]{1,12}$/.test(thread) || !/^[a-f0-9-]{36}$/.test(id))
|
|
51
|
+
return {};
|
|
52
|
+
// R5-A2 do CHECK 5: a mesma conferencia D-4 da reentrada. O par que o daemon do `claude --bg` vazou e um
|
|
53
|
+
// despacho real, que o ledger confirma; so a sessao que o recebeu de fato o valida.
|
|
54
|
+
let confirmado = false;
|
|
55
|
+
try {
|
|
56
|
+
confirmado = (0, conducao_1.identidadeDoAmbiente)(thread, process.env, raiz) === id;
|
|
57
|
+
}
|
|
58
|
+
catch { /* thread de outro projeto ou ilegivel: sem prova, sem rastro */ }
|
|
59
|
+
if (confirmado) {
|
|
60
|
+
try {
|
|
61
|
+
confirmado = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread)).some((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada &&
|
|
62
|
+
e.identidade?.dispatchId === id);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
confirmado = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return confirmado ? { despachoNoAmbiente: { thread, dispatchId: id } } : {};
|
|
69
|
+
}
|
|
70
|
+
/** O canal do processo; canal fora do registro nao impede a decisao, e fica dito como tal. */
|
|
71
|
+
function canalSeguro() {
|
|
72
|
+
try {
|
|
73
|
+
return (0, conducao_1.canalDoProcesso)();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return 'desconhecido';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
43
79
|
/** D3/M7: o p90 das 155 fases ja medidas. Acima disso a fase esta no decil mais alto do projeto. */
|
|
44
80
|
exports.LIMIAR_DE_DECISOES_POR_FASE = 13;
|
|
45
81
|
/** Os nomes que o rastro teve antes de ser tipado (M6), na ordem em que valem na leitura. */
|
|
@@ -110,6 +146,12 @@ function registrarDecisao(raiz, threadId, entrada) {
|
|
|
110
146
|
const evento = (0, ledger_1.registrar)(dir, t.id, ledger_1.TIPOS_DE_EVENTO.decisaoAutonoma, {
|
|
111
147
|
contratoDecisao: exports.CONTRATO_DECISAO_AUTONOMA, fase: pedido.fase, decisao: pedido.decidido, ...rastro,
|
|
112
148
|
pedido, ...(entrada.reverte ? { reverte: entrada.reverte } : {}),
|
|
149
|
+
origem: entrada.origem ?? 'cli', ...(entrada.host ? { host: entrada.host } : {}),
|
|
150
|
+
// RM-037 (S5 do CHECK): a porta e quem chamou, pelo processo e nao pelo texto de `quemDecidiu`. A sessao
|
|
151
|
+
// despachada carrega a identidade do despacho no ambiente; o dono no terminal, nao.
|
|
152
|
+
canal: entrada.origem === 'mcp' && entrada.host ? entrada.host : canalSeguro(),
|
|
153
|
+
despacho: entrada.despacho !== undefined ? entrada.despacho : (0, conducao_1.identidadeDoAmbiente)(t.id, process.env, raiz),
|
|
154
|
+
...despachoDeOutraThread(raiz, t.id),
|
|
113
155
|
});
|
|
114
156
|
return { pedido, evento };
|
|
115
157
|
});
|