@orkastery/cli 0.5.0 → 0.5.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/adapters/claude-code/README.md +11 -2
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/openclaw/README.md +11 -0
- package/adapters/openclaw/dist/index.js +1 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/adapters/openclaw/src/index.ts +1 -1
- package/dist/adapters/claude-bg.js +20 -6
- package/dist/auditrun.js +1 -1
- package/dist/claim-lint.js +3 -1
- package/dist/docs.js +71 -3
- package/dist/doctor.js +209 -32
- package/dist/entrega-pr.js +7 -0
- package/dist/fabrica-estado.js +21 -3
- package/dist/fix.js +3 -3
- package/dist/hitl-resumo.js +24 -1
- package/dist/hosts.js +7 -3
- package/dist/index.js +42 -14
- package/dist/init.js +11 -5
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +181 -27
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +16 -0
- package/dist/ledger.js +9 -0
- package/dist/licoes.js +5 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +3 -0
- package/dist/manifest.js +24 -0
- package/dist/mcp-artifacts.js +2 -0
- package/dist/mcp-git.js +60 -12
- package/dist/mcp-grafo-worker.js +91 -0
- package/dist/mcp-grafo.js +233 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +9 -0
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/network-roadmap.js +5 -5
- package/dist/ocupacao.js +6 -2
- package/dist/onboarding.js +9 -4
- package/dist/orquestracao.js +2 -1
- package/dist/parado-no-condutor.js +933 -0
- package/dist/policies.js +27 -2
- package/dist/prova-ativacao.js +345 -0
- package/dist/pulse-delivery.js +27 -6
- package/dist/pulse.js +53 -3
- package/dist/roadmap-status.js +142 -7
- package/dist/runtime-context.js +5 -1
- package/dist/sessoes-inventario.js +7 -1
- package/dist/sessoes.js +4 -1
- package/dist/ship.js +25 -1
- package/dist/thread.js +21 -2
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +29 -6
- package/eval/casos/onboarding.json +1 -1
- package/package.json +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/policies.js
CHANGED
|
@@ -96,6 +96,17 @@ exports.POLICIES_CONHECIDAS = {
|
|
|
96
96
|
descricao: 'avisa quando a branch da thread esta atras da base',
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
|
+
/**
|
|
100
|
+
* Ensaio da 0.5.0: a thread criada sem `--worktree auto` entrega a base para a base, e a correcao
|
|
101
|
+
* repetia o proprio `ork ship`. A thread que ainda nao passou do GO ganha a branch com
|
|
102
|
+
* `ork worktree ensure`. Depois do GO, nao: os commits ja estao na base, a branch nova nasce com eles
|
|
103
|
+
* e o ship sem delta empurraria a base direto, que e o que esta policy barra (CHECK, rodada 1).
|
|
104
|
+
*/
|
|
105
|
+
function correcaoSemBranchDaThread(threadId, base) {
|
|
106
|
+
return `entregue a branch da thread (ork/<slug>) com --para ${base || '<base>'}; sem worktree e antes do GO, ` +
|
|
107
|
+
`crie-a com ork worktree ensure ${threadId || '<thread>'} (thread nova: --worktree auto); depois do GO, ` +
|
|
108
|
+
'os commits ja estao na base e o ship nao os separa: leve-os para a branch da thread antes de entregar';
|
|
109
|
+
}
|
|
99
110
|
function severidade(bruta) {
|
|
100
111
|
if (bruta === 'block' || bruta === 'warn' || bruta === 'off')
|
|
101
112
|
return bruta;
|
|
@@ -158,7 +169,7 @@ function avaliarPolicies(manifesto, ctx) {
|
|
|
158
169
|
severidade: sev,
|
|
159
170
|
motivo: 'policy.violation',
|
|
160
171
|
detalhe: `origem e destino sao a mesma branch ("${de}"): isso e push direto na base, nao merge de thread`,
|
|
161
|
-
correcao:
|
|
172
|
+
correcao: correcaoSemBranchDaThread(ctx.threadId, base),
|
|
162
173
|
});
|
|
163
174
|
}
|
|
164
175
|
else if (de && base && de === base) {
|
|
@@ -167,7 +178,21 @@ function avaliarPolicies(manifesto, ctx) {
|
|
|
167
178
|
severidade: sev,
|
|
168
179
|
motivo: 'policy.violation',
|
|
169
180
|
detalhe: `a origem "${de}" e a propria branch base do projeto: nenhuma branch de thread foi usada`,
|
|
170
|
-
correcao:
|
|
181
|
+
correcao: correcaoSemBranchDaThread(ctx.threadId, base),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else if (ctx.semDeltaComBaseAFrente === true) {
|
|
185
|
+
// Fatia 2 do ensaio da 0.5.0 (P9): a thread que perdeu a branch depois do GO ganha uma com os commits
|
|
186
|
+
// ja na base; o ship ve nada a mergear e empurraria a base, que e o que esta policy barra.
|
|
187
|
+
const remoto = ctx.remoto ?? 'origin';
|
|
188
|
+
violacoes.push({
|
|
189
|
+
policy: nome,
|
|
190
|
+
severidade: sev,
|
|
191
|
+
motivo: 'policy.violation',
|
|
192
|
+
detalhe: `a branch "${de}" nao traz commit alem de "${para}", e ${para} local tem commit que ${remoto}/${para} nao tem: ` +
|
|
193
|
+
'o ship empurraria a base direto, sem merge de thread',
|
|
194
|
+
correcao: `leve os commits que estao so em ${para} local para a branch da thread antes de entregar, ou entregue-os ` +
|
|
195
|
+
`pela thread que os criou (conferido em refs/remotes/${remoto}/${para}, sem rede)`,
|
|
171
196
|
});
|
|
172
197
|
}
|
|
173
198
|
continue;
|
|
@@ -0,0 +1,345 @@
|
|
|
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.ENTRADAS = exports.HOSTS_DA_PROVA = void 0;
|
|
37
|
+
exports.transcriptDoClaude = transcriptDoClaude;
|
|
38
|
+
exports.transcriptDoOpenclaw = transcriptDoOpenclaw;
|
|
39
|
+
exports.extrairSnapshot = extrairSnapshot;
|
|
40
|
+
exports.conferirProva = conferirProva;
|
|
41
|
+
exports.redigir = redigir;
|
|
42
|
+
exports.redigirObjeto = redigirObjeto;
|
|
43
|
+
/**
|
|
44
|
+
* RM-032: conferência da prova de ativação por host, sem host.
|
|
45
|
+
*
|
|
46
|
+
* O roteiro `core/scripts/prova-ativacao.cjs` abre uma sessão nova e não interativa no host
|
|
47
|
+
* (Claude Code ou OpenClaw), diz `orkastery maestro` e entrega aqui o transcript. Esta
|
|
48
|
+
* conferência olha só o que é determinístico: qual ferramenta o modelo chamou, o resultado que
|
|
49
|
+
* ela devolveu (contra o contrato da entrada) e o projeto lido. Da resposta final em texto
|
|
50
|
+
* livre, exige apenas que cite o projeto e não repita o incidente de 29/09 ("roadmap vazio").
|
|
51
|
+
*/
|
|
52
|
+
const fs = __importStar(require("node:fs"));
|
|
53
|
+
const path = __importStar(require("node:path"));
|
|
54
|
+
const maestro_contract_1 = require("./maestro-contract");
|
|
55
|
+
exports.HOSTS_DA_PROVA = ['claude-code', 'openclaw'];
|
|
56
|
+
/**
|
|
57
|
+
* As entradas da frase em cada host. No Claude, a tool MCP do servidor `orkastery` fixado no
|
|
58
|
+
* projeto (MAESTRO_HOST_SURFACES em hosts.ts). No OpenClaw 0.5.0 (RM-054, fatia 2), a frase sem
|
|
59
|
+
* projeto vai a `ork_network_roadmap`, a única tool da extensão no perfil `coding`; com projeto
|
|
60
|
+
* nomeado, a `ork_maestro`. `ork maestro` pelo shell do host é desvio: o CLI resolve o projeto
|
|
61
|
+
* pelo diretório atual, a classe de erro do incidente.
|
|
62
|
+
*/
|
|
63
|
+
exports.ENTRADAS = {
|
|
64
|
+
'claude-code': [{ ferramenta: 'mcp__orkastery__ork_maestro', contrato: 'maestro' }],
|
|
65
|
+
openclaw: [{ ferramenta: 'ork_network_roadmap', contrato: 'rede' }, { ferramenta: 'ork_maestro', contrato: 'maestro' }],
|
|
66
|
+
};
|
|
67
|
+
const SHELL_DO_HOST = { 'claude-code': 'Bash', openclaw: 'exec' };
|
|
68
|
+
const ORK_MAESTRO_NO_SHELL = /(^|[\s;&|(/])ork(\s+(--projeto|--project|-p)(\s+|=)\S+)?\s+maestro\b/;
|
|
69
|
+
function linhasJson(texto) {
|
|
70
|
+
const eventos = [];
|
|
71
|
+
for (const linha of texto.split('\n')) {
|
|
72
|
+
const limpa = linha.trim();
|
|
73
|
+
if (!limpa.startsWith('{'))
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
eventos.push(JSON.parse(limpa));
|
|
77
|
+
}
|
|
78
|
+
catch { /* linha de log do host, não evento */ }
|
|
79
|
+
}
|
|
80
|
+
return eventos;
|
|
81
|
+
}
|
|
82
|
+
function textoDoConteudo(conteudo) {
|
|
83
|
+
if (typeof conteudo === 'string')
|
|
84
|
+
return conteudo;
|
|
85
|
+
if (!Array.isArray(conteudo))
|
|
86
|
+
return null;
|
|
87
|
+
const partes = conteudo.filter((c) => !!c && typeof c === 'object' && c.type === 'text' && typeof c.text === 'string');
|
|
88
|
+
return partes.length ? partes.map(p => p.text).join('\n') : null;
|
|
89
|
+
}
|
|
90
|
+
function classificar(host, ferramenta, argumentos) {
|
|
91
|
+
const entrada = exports.ENTRADAS[host].find(e => e.ferramenta === ferramenta);
|
|
92
|
+
if (entrada)
|
|
93
|
+
return { via: 'entrada', contrato: entrada.contrato };
|
|
94
|
+
const comando = argumentos?.command;
|
|
95
|
+
if (ferramenta === SHELL_DO_HOST[host] && typeof comando === 'string' && ORK_MAESTRO_NO_SHELL.test(comando))
|
|
96
|
+
return { via: 'shell', contrato: null };
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/** Transcript `claude -p --output-format stream-json --verbose`. */
|
|
100
|
+
function transcriptDoClaude(streamJson) {
|
|
101
|
+
const eventos = linhasJson(streamJson);
|
|
102
|
+
let ferramentasExpostas = null;
|
|
103
|
+
let respostaFinal = null;
|
|
104
|
+
const pedidas = new Map();
|
|
105
|
+
const chamadas = [];
|
|
106
|
+
for (const e of eventos) {
|
|
107
|
+
if (e.type === 'system' && e.subtype === 'init' && Array.isArray(e.tools))
|
|
108
|
+
ferramentasExpostas = e.tools.filter((t) => typeof t === 'string');
|
|
109
|
+
const mensagem = e.message;
|
|
110
|
+
const blocos = Array.isArray(mensagem?.content) ? mensagem.content : [];
|
|
111
|
+
if (e.type === 'assistant') {
|
|
112
|
+
for (const b of blocos) {
|
|
113
|
+
if (b?.type !== 'tool_use' || typeof b.name !== 'string' || typeof b.id !== 'string')
|
|
114
|
+
continue;
|
|
115
|
+
const tipo = classificar('claude-code', b.name, b.input);
|
|
116
|
+
if (!tipo)
|
|
117
|
+
continue;
|
|
118
|
+
const chamada = { ferramenta: b.name, ...tipo, argumentos: b.input ?? null, resultado: null, erro: false };
|
|
119
|
+
pedidas.set(b.id, chamada);
|
|
120
|
+
chamadas.push(chamada);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (e.type === 'user') {
|
|
124
|
+
for (const b of blocos) {
|
|
125
|
+
if (b?.type !== 'tool_result' || typeof b.tool_use_id !== 'string')
|
|
126
|
+
continue;
|
|
127
|
+
const chamada = pedidas.get(b.tool_use_id);
|
|
128
|
+
if (!chamada)
|
|
129
|
+
continue;
|
|
130
|
+
chamada.resultado = textoDoConteudo(b.content);
|
|
131
|
+
chamada.erro = b.is_error === true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (e.type === 'result' && typeof e.result === 'string')
|
|
135
|
+
respostaFinal = e.result;
|
|
136
|
+
}
|
|
137
|
+
return { ferramentasExpostas, chamadas, respostaFinal };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Trajetória do OpenClaw (`openclaw sessions export-trajectory`, `events.jsonl`): eventos
|
|
141
|
+
* `tool.call`/`tool.result` da origem `runtime`. As ferramentas expostas e a resposta final vêm
|
|
142
|
+
* do `openclaw agent --json` (`meta.systemPromptReport.tools.entries`, `payloads[].text`).
|
|
143
|
+
*/
|
|
144
|
+
function transcriptDoOpenclaw(eventsJsonl, agenteJson) {
|
|
145
|
+
const pedidas = new Map();
|
|
146
|
+
const chamadas = [];
|
|
147
|
+
for (const e of linhasJson(eventsJsonl)) {
|
|
148
|
+
if (e.source !== 'runtime')
|
|
149
|
+
continue;
|
|
150
|
+
const d = (e.data ?? {});
|
|
151
|
+
if (typeof d.toolCallId !== 'string' || typeof d.name !== 'string')
|
|
152
|
+
continue;
|
|
153
|
+
if (e.type === 'tool.call') {
|
|
154
|
+
const tipo = classificar('openclaw', d.name, d.args);
|
|
155
|
+
if (!tipo)
|
|
156
|
+
continue;
|
|
157
|
+
const chamada = { ferramenta: d.name, ...tipo, argumentos: d.args ?? null, resultado: null, erro: false };
|
|
158
|
+
pedidas.set(d.toolCallId, chamada);
|
|
159
|
+
chamadas.push(chamada);
|
|
160
|
+
}
|
|
161
|
+
else if (e.type === 'tool.result') {
|
|
162
|
+
const chamada = pedidas.get(d.toolCallId);
|
|
163
|
+
if (!chamada)
|
|
164
|
+
continue;
|
|
165
|
+
chamada.resultado = textoDoConteudo(d.result?.content);
|
|
166
|
+
chamada.erro = d.success !== true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const agente = (agenteJson ?? {});
|
|
170
|
+
const entradas = agente.meta?.systemPromptReport?.tools?.entries;
|
|
171
|
+
const ferramentasExpostas = Array.isArray(entradas) ? entradas.map(t => t?.name).filter((n) => typeof n === 'string') : null;
|
|
172
|
+
const textos = (agente.payloads ?? []).map(p => p?.text).filter((t) => typeof t === 'string');
|
|
173
|
+
return { ferramentasExpostas, chamadas, respostaFinal: textos.length ? textos.join('\n') : null };
|
|
174
|
+
}
|
|
175
|
+
/** O primeiro objeto `ork.maestro-snapshot/v1` do texto (o host pode juntar stderr ao stdout). */
|
|
176
|
+
function extrairSnapshot(texto) {
|
|
177
|
+
const inicio = texto.indexOf('{"schema":"ork.maestro-snapshot/v1"');
|
|
178
|
+
if (inicio < 0)
|
|
179
|
+
return null;
|
|
180
|
+
let profundidade = 0, emTexto = false, escape = false;
|
|
181
|
+
for (let i = inicio; i < texto.length; i++) {
|
|
182
|
+
const c = texto[i];
|
|
183
|
+
if (emTexto) {
|
|
184
|
+
if (escape)
|
|
185
|
+
escape = false;
|
|
186
|
+
else if (c === '\\')
|
|
187
|
+
escape = true;
|
|
188
|
+
else if (c === '"')
|
|
189
|
+
emTexto = false;
|
|
190
|
+
}
|
|
191
|
+
else if (c === '"')
|
|
192
|
+
emTexto = true;
|
|
193
|
+
else if (c === '{')
|
|
194
|
+
profundidade++;
|
|
195
|
+
else if (c === '}' && --profundidade === 0) {
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(texto.slice(inicio, i + 1));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
function expandirHome(raiz, home) {
|
|
207
|
+
return raiz === '~' ? home : raiz.startsWith('~/') ? path.join(home, raiz.slice(2)) : raiz;
|
|
208
|
+
}
|
|
209
|
+
const ROADMAP_VAZIO = /\broadmap\b:?((?:\s+[^\s.!?:;,]+){0,4}?)\s+(vazio|empty)\b/giu;
|
|
210
|
+
const NEGACAO = /(^|[^\p{L}])(n[ãa]o|nunca|jamais|not|never|isn't|doesn't|nem)([^\p{L}]|$)/iu;
|
|
211
|
+
/**
|
|
212
|
+
* A frase que conclui "roadmap vazio". A negação só conta quando governa a conclusão: dentro do
|
|
213
|
+
* trecho ("the roadmap is not empty") ou nas até seis palavras antes dele, sem atravessar
|
|
214
|
+
* pontuação ("zero threads não quer dizer roadmap vazio"). "O roadmap está vazio e não há
|
|
215
|
+
* threads" e "Não há itens: o roadmap está vazio" concluem.
|
|
216
|
+
*/
|
|
217
|
+
function concluiRoadmapVazio(texto) {
|
|
218
|
+
for (const m of texto.matchAll(ROADMAP_VAZIO)) {
|
|
219
|
+
const antes = texto.slice(0, m.index).split(/[.!?:;,\n]/).pop().trim().split(/\s+/).slice(-6).join(' ');
|
|
220
|
+
if (!NEGACAO.test(antes) && !NEGACAO.test(m[1]))
|
|
221
|
+
return `${antes} ${m[0]}`.trim();
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const canonica = (p) => { try {
|
|
226
|
+
return fs.realpathSync(p);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return path.resolve(p);
|
|
230
|
+
} };
|
|
231
|
+
/** Nome inteiro, não pedaço de palavra: a abreviação `pav` não casa com "pavimento". */
|
|
232
|
+
const nomeia = (texto, nome) => new RegExp(`(^|[^\\p{L}\\p{N}_-])${nome.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\p{L}\\p{N}_-]|$)`, 'iu').test(texto);
|
|
233
|
+
/**
|
|
234
|
+
* O texto de `ork network roadmap` (`textoDoPanoramaDaRede`): a primeira linha diz de onde foi
|
|
235
|
+
* lido, a segunda quais projetos foram consultados (`<nome> (clone em <raiz>)`) e o bloco
|
|
236
|
+
* "Não consultado:" o que ficou de fora.
|
|
237
|
+
*/
|
|
238
|
+
function lerPanoramaDaRede(texto) {
|
|
239
|
+
const linhas = texto.split('\n');
|
|
240
|
+
const i = linhas.findIndex(l => /^Panorama da rede lido de /.test(l));
|
|
241
|
+
if (i < 0 || !/^Consultado: /.test(linhas[i + 1] ?? '') || linhas[i + 2] !== 'Não consultado:')
|
|
242
|
+
return null;
|
|
243
|
+
const naoConsultado = [];
|
|
244
|
+
for (const l of linhas.slice(i + 3)) {
|
|
245
|
+
if (!l.startsWith('• '))
|
|
246
|
+
break;
|
|
247
|
+
naoConsultado.push(l.slice(2));
|
|
248
|
+
}
|
|
249
|
+
return { cabecalho: linhas[i], consultado: linhas[i + 1], naoConsultado };
|
|
250
|
+
}
|
|
251
|
+
function conferirProva(host, t, esperado) {
|
|
252
|
+
const conferencias = [];
|
|
253
|
+
const conferir = (id, ok, detalhe) => { conferencias.push({ id, ok, detalhe }); return ok; };
|
|
254
|
+
const nomes = exports.ENTRADAS[host].map(e => e.ferramenta);
|
|
255
|
+
const expostas = nomes.filter(n => t.ferramentasExpostas?.includes(n));
|
|
256
|
+
conferir('entrada.exposta', expostas.length > 0, t.ferramentasExpostas === null ? 'o transcript não lista as ferramentas expostas'
|
|
257
|
+
: expostas.length ? `${expostas.join(', ')} exposta(s) ao modelo`
|
|
258
|
+
: `${nomes.join(' e ')} ausente(s) entre ${t.ferramentasExpostas.length} ferramentas expostas`);
|
|
259
|
+
// A última chamada contratada com resultado é a que vale (uma nova tentativa depois de erro não
|
|
260
|
+
// reprova); qualquer `ork maestro` pelo shell reprova, mesmo ao lado da chamada contratada.
|
|
261
|
+
const contratadas = t.chamadas.filter(c => c.via === 'entrada');
|
|
262
|
+
const contratada = [...contratadas].reverse().find(c => c.resultado !== null && !c.erro) ?? contratadas.at(-1);
|
|
263
|
+
const desvio = t.chamadas.find(c => c.via === 'shell');
|
|
264
|
+
const comando = desvio && JSON.stringify(desvio.argumentos?.command);
|
|
265
|
+
conferir('entrada.chamada', !!contratada && !desvio, contratada && desvio ? `${contratada.ferramenta} chamada, mas também desvio: ${desvio.ferramenta} ${comando}`
|
|
266
|
+
: contratada ? `${contratada.ferramenta} chamada`
|
|
267
|
+
: desvio ? `desvio: ${desvio.ferramenta} ${comando} em vez de ${nomes.join(' ou ')}`
|
|
268
|
+
: `${nomes.join(' ou ')} não foi chamada`);
|
|
269
|
+
let snapshot = null;
|
|
270
|
+
let rede = null;
|
|
271
|
+
if (contratada && conferir('resultado.sem-erro', contratada.resultado !== null && !contratada.erro, contratada.resultado === null ? 'a chamada não teve resultado' : contratada.erro ? `erro: ${contratada.resultado.slice(0, 300)}` : 'resultado sem erro')) {
|
|
272
|
+
if (contratada.contrato === 'maestro') {
|
|
273
|
+
try {
|
|
274
|
+
snapshot = (0, maestro_contract_1.validateMaestroSnapshot)(extrairSnapshot(contratada.resultado));
|
|
275
|
+
conferir('resultado.contrato', true, `${snapshot.schema} validado`);
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
conferir('resultado.contrato', false, `fora do contrato: ${e.message.slice(0, 300)}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
rede = lerPanoramaDaRede(contratada.resultado);
|
|
283
|
+
conferir('resultado.contrato', rede !== null, rede ? 'texto do ork.network-roadmap/v1: cabeçalho, Consultado e Não consultado'
|
|
284
|
+
: 'fora do contrato: sem o cabeçalho "Panorama da rede lido de", "Consultado:" e "Não consultado:"');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (snapshot) {
|
|
288
|
+
const p = snapshot.project;
|
|
289
|
+
const exibida = p.root === undefined || p.root.includes('[caminho privado]') ? null : canonica(expandirHome(p.root, esperado.home));
|
|
290
|
+
const mesmaCopia = p.fingerprint === esperado.projeto.fingerprint && (exibida === null || exibida === canonica(esperado.projeto.raiz));
|
|
291
|
+
conferir('resultado.projeto', p.name === esperado.projeto.nome && mesmaCopia, `leu ${p.name} (${p.origin}) em ${p.root ?? 'raiz não exibida'}, impressão ${p.fingerprint.slice(0, 12)}; ` +
|
|
292
|
+
`esperado ${esperado.projeto.nome}, impressão ${esperado.projeto.fingerprint.slice(0, 12)}`);
|
|
293
|
+
const lacunas = snapshot.notConsulted ?? [];
|
|
294
|
+
conferir('resultado.nao-consultado', lacunas.length > 0, lacunas.length ? `declara o que não leu: ${lacunas.length} item(ns)` : 'notConsulted ausente: zero threads pode virar "roadmap vazio"');
|
|
295
|
+
}
|
|
296
|
+
if (rede) {
|
|
297
|
+
// "Consultado: a (clone em X); b (clone em Y)": vale o projeto esperado na raiz esperada.
|
|
298
|
+
const lidos = [...rede.consultado.replace(/^Consultado: /, '').matchAll(/(?:^|; )(.+?) \(clone em ([^)]+)\)/g)];
|
|
299
|
+
const leu = lidos.some(([, nome, raiz]) => nome === esperado.projeto.nome &&
|
|
300
|
+
canonica(expandirHome(raiz, esperado.home)) === canonica(esperado.projeto.raiz));
|
|
301
|
+
conferir('resultado.projeto', leu, `${rede.consultado.slice(0, 300)}; esperado ${esperado.projeto.nome} (clone em ${esperado.projeto.raiz})`);
|
|
302
|
+
conferir('resultado.nao-consultado', rede.naoConsultado.length > 0, rede.naoConsultado.length ? `declara o que não leu: ${rede.naoConsultado.length} item(ns)` : 'sem o bloco do que não foi consultado');
|
|
303
|
+
}
|
|
304
|
+
const resposta = t.respostaFinal ?? '';
|
|
305
|
+
const citaProjeto = [esperado.projeto.nome, esperado.projeto.id].some(n => nomeia(resposta, n));
|
|
306
|
+
conferir('resposta.cita-projeto', citaProjeto, t.respostaFinal === null ? 'sem resposta final' : citaProjeto ? 'a resposta nomeia o projeto lido' : 'a resposta não nomeia o projeto lido');
|
|
307
|
+
const vazio = concluiRoadmapVazio(resposta);
|
|
308
|
+
conferir('resposta.sem-roadmap-vazio', vazio === null, vazio ? `a resposta conclui "roadmap vazio" a partir do panorama (incidente de 29/09): ${vazio.slice(0, 200)}` : 'sem concluir "roadmap vazio" a partir do panorama');
|
|
309
|
+
return {
|
|
310
|
+
ok: conferencias.every(c => c.ok),
|
|
311
|
+
conferencias,
|
|
312
|
+
snapshot: snapshot && {
|
|
313
|
+
schema: snapshot.schema, projeto: snapshot.project,
|
|
314
|
+
secoes: Object.fromEntries(Object.entries(snapshot.sections).map(([nome, s]) => [nome, s.state])),
|
|
315
|
+
notConsulted: snapshot.notConsulted ?? [],
|
|
316
|
+
},
|
|
317
|
+
rede,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Redação antes de qualquer gravação: o recibo vai ao repositório. Cobre tokens com prefixo
|
|
322
|
+
* conhecido, `Bearer`/`Basic`, variáveis de ambiente com nome de segredo e valores de chaves JSON
|
|
323
|
+
* com nome de segredo. Para objetos, use `redigirObjeto`, que aplica a regra a cada texto antes
|
|
324
|
+
* da serialização (JSON dentro de stdout escaparia depois dela).
|
|
325
|
+
*/
|
|
326
|
+
const CHAVE_DE_SEGREDO = /token|secret|password|passwd|api[_-]?key|apikey|credential|authorization|cookie/i;
|
|
327
|
+
function redigir(texto) {
|
|
328
|
+
return texto
|
|
329
|
+
.replace(/\b(sk-ant-[A-Za-z0-9_-]+|sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{16,}|[sr]k_live_[A-Za-z0-9]{10,}|AIza[0-9A-Za-z_-]{20,})/g, '[REDIGIDO]')
|
|
330
|
+
.replace(/\b(gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[abprs]-[A-Za-z0-9-]{10,})/g, '[REDIGIDO]')
|
|
331
|
+
.replace(/\b(Bearer|Basic)(\s+)[A-Za-z0-9._~+/-]{8,}=*/gi, '$1$2[REDIGIDO]')
|
|
332
|
+
// O valor para em aspas, barra invertida e espaço: aplicada a JSON, a regra não come o fecho.
|
|
333
|
+
.replace(/\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*\s*=\s*)(?!\d+\b)("[^"\\]*"|'[^'\\]*'|[^\s"'\\]+)/g, '$1[REDIGIDO]')
|
|
334
|
+
.replace(/("[^"]*(?:token|secret|password|passwd|api[_-]?key|apikey|credential|authorization|cookie)[^"]*"\s*:\s*)"(?:[^"\\]|\\.)*"/gi, '$1"[REDIGIDO]"');
|
|
335
|
+
}
|
|
336
|
+
function redigirObjeto(valor) {
|
|
337
|
+
if (typeof valor === 'string')
|
|
338
|
+
return redigir(valor);
|
|
339
|
+
if (Array.isArray(valor))
|
|
340
|
+
return valor.map(v => redigirObjeto(v));
|
|
341
|
+
if (valor && typeof valor === 'object') {
|
|
342
|
+
return Object.fromEntries(Object.entries(valor).map(([k, v]) => [k, typeof v === 'string' && CHAVE_DE_SEGREDO.test(k) ? '[REDIGIDO]' : redigirObjeto(v)]));
|
|
343
|
+
}
|
|
344
|
+
return valor;
|
|
345
|
+
}
|
package/dist/pulse-delivery.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.adquirirLockMonitor = void 0;
|
|
36
|
+
exports.RETEM_PARADO_MS = exports.adquirirLockMonitor = void 0;
|
|
37
37
|
exports.assinaturaPulse = assinaturaPulse;
|
|
38
38
|
exports.assinaturaDoResumo = assinaturaDoResumo;
|
|
39
39
|
exports.mensagemPulse = mensagemPulse;
|
|
@@ -63,6 +63,8 @@ const pulse_cadencia_1 = require("./pulse-cadencia");
|
|
|
63
63
|
// O lock dos monitores mudou de modulo (I-41, GO-FIX 1); quem o importava daqui continua importando.
|
|
64
64
|
var monitor_lock_2 = require("./monitor-lock");
|
|
65
65
|
Object.defineProperty(exports, "adquirirLockMonitor", { enumerable: true, get: function () { return monitor_lock_2.adquirirLockMonitor; } });
|
|
66
|
+
/** RM-037 (fatia 4): por quanto tempo a parada ja informada continua sabida depois de sumir de uma batida. */
|
|
67
|
+
exports.RETEM_PARADO_MS = 6 * 60 * 60 * 1000;
|
|
66
68
|
/**
|
|
67
69
|
* I-41 (D9): a assinatura de deduplicacao NAO inclui `apresentacao`.
|
|
68
70
|
*
|
|
@@ -183,9 +185,24 @@ function varrerPulse(opcoes) {
|
|
|
183
185
|
const esperasFora = outras.flatMap(m => m.esperando.map(e => `maquina:${m.maquina}:${e.thread}:${e.fase}`));
|
|
184
186
|
for (const k of esperasFora)
|
|
185
187
|
presentes.add(k);
|
|
186
|
-
|
|
188
|
+
// RM-037 (fatia 4, D7): o trabalho parado no condutor e noticia uma vez por thread e caso, como a
|
|
189
|
+
// decisao informada: sai na janela da cadencia do dono e nunca abre pedido.
|
|
190
|
+
const parados = pulse.paradoNoCondutor ?? [];
|
|
191
|
+
const chaveDoParado = (p) => `parado:${p.thread}:${p.caso}`;
|
|
192
|
+
for (const p of parados)
|
|
193
|
+
presentes.add(chaveDoParado(p));
|
|
194
|
+
// A parada que some por uma batida (gh que falhou, um `ork verify` do condutor) e volta nao e noticia
|
|
195
|
+
// nova: a marca fica guardada por `RETEM_PARADO_MS` depois da ultima vez que a parada apareceu.
|
|
196
|
+
const guardada = (id, valor) => id.startsWith('parado:') &&
|
|
197
|
+
Date.parse(quando) - Number(valor.split('|')[1]) < exports.RETEM_PARADO_MS;
|
|
198
|
+
vistas = Object.fromEntries(Object.entries(vistas).filter(([id, valor]) => presentes.has(id) || guardada(id, valor)));
|
|
187
199
|
const esperaNovaFora = esperasFora.some(k => vistas[k] === undefined);
|
|
188
200
|
const decisoesNovas = decisoes.filter(d => vistas[chaveDaDecisao(d.id)] === undefined);
|
|
201
|
+
const paradosNovos = parados.filter(p => vistas[chaveDoParado(p)] === undefined);
|
|
202
|
+
// A marca guarda o instante em milissegundos: e dado de maquina, nao texto para o dono.
|
|
203
|
+
for (const p of parados)
|
|
204
|
+
if (vistas[chaveDoParado(p)] !== undefined)
|
|
205
|
+
vistas[chaveDoParado(p)] = `informada|${Date.parse(quando)}`;
|
|
189
206
|
// I-41 (GO-FIX 1): a thread que ja recebeu a pergunta e ainda nao respondeu nao e noticia. A
|
|
190
207
|
// mudanca no item dela (o pedido que o sim do dono abriu) e obra nossa, e avisar sobre ela seria
|
|
191
208
|
// mandar ao dono um resumo sobre a pergunta que ele acabou de receber.
|
|
@@ -205,7 +222,8 @@ function varrerPulse(opcoes) {
|
|
|
205
222
|
!config.argumentos.every(a => typeof a === 'string') || !config.argumentos.some(a => a.includes('{{mensagem}}'))) {
|
|
206
223
|
throw new Error('transporte requer executavel e argumentos com {{mensagem}}');
|
|
207
224
|
}
|
|
208
|
-
|
|
225
|
+
// A mensagem entra como texto: `$&` e `$'` nela nao sao padrao de substituicao (CHECK da RM-037, fatia 4).
|
|
226
|
+
const r = (0, node_child_process_1.spawnSync)(config.executavel, config.argumentos.map(a => a.replaceAll('{{mensagem}}', () => mensagem)), { cwd: raiz, encoding: 'utf8', input: mensagem, timeout: 30000, maxBuffer: 1024 * 1024 });
|
|
209
227
|
entregue = r.status === 0;
|
|
210
228
|
}
|
|
211
229
|
if (!entregue)
|
|
@@ -229,12 +247,13 @@ function varrerPulse(opcoes) {
|
|
|
229
247
|
// I-50: pergunta nova sai na hora; o resto (status, decisoes informadas) espera a janela da
|
|
230
248
|
// cadencia do dono, e a novidade que esperou continua nao vista ate sair.
|
|
231
249
|
const noPrazo = !opcoes.comCadencia || (0, pulse_cadencia_1.janelaAberta)(cadencia, (0, pulse_cadencia_1.lerUltimoResumo)(opcoes.raiz, dir), quando);
|
|
232
|
-
const
|
|
233
|
-
|
|
250
|
+
const informacaoNova = novas > 0 || decisoesNovas.length > 0 || paradosNovos.length > 0;
|
|
251
|
+
const adiada = !conjuntoNovo && !esperaNovaFora && informacaoNova && !noPrazo;
|
|
252
|
+
if (conjuntoNovo || esperaNovaFora || (noPrazo && informacaoNova)) {
|
|
234
253
|
const guardadas = anterior ? fila.candidatos.filter(c => anterior.candidatos.some(a => a.thread === c.thread)).length : 0;
|
|
235
254
|
const resumo = (0, hitl_resumo_1.resumirHitl)(itens, { quando, janelaMin, prontas: fila.candidatos.length, acumuladas: guardadas,
|
|
236
255
|
consertos: fila.consertos, atoDoItem: i => fila.atos.get(`${i.thread}|${i.fase}`),
|
|
237
|
-
decisoes: decisoesNovas, acimaDoLimiar: pulse.acimaDoLimiar ?? [], outrasMaquinas: outras });
|
|
256
|
+
decisoes: decisoesNovas, acimaDoLimiar: pulse.acimaDoLimiar ?? [], outrasMaquinas: outras, paradosNoCondutor: parados });
|
|
238
257
|
// Pedir licenca para mandar zero perguntas era o defeito: sem pergunta, nao ha codigo.
|
|
239
258
|
// I-50: cadencia curta nao encurta o prazo do dono para responder ao resumo (minimo de 60 min).
|
|
240
259
|
const consentimento = fila.candidatos.length ? (0, monitor_lock_1.comLockDaConversa)(dir, () => (0, pulse_consentimento_1.abrirConsentimento)(raiz, { quando,
|
|
@@ -252,6 +271,8 @@ function varrerPulse(opcoes) {
|
|
|
252
271
|
vistas[chaveDaDecisao(d.id)] = 'informada';
|
|
253
272
|
for (const k of esperasFora)
|
|
254
273
|
vistas[k] = 'informada';
|
|
274
|
+
for (const p of parados)
|
|
275
|
+
vistas[chaveDoParado(p)] = `informada|${Date.parse(quando)}`;
|
|
255
276
|
(0, pulse_cadencia_1.gravarUltimoResumo)(opcoes.raiz, quando, dir);
|
|
256
277
|
}
|
|
257
278
|
gravarCache(cache, vistas);
|
package/dist/pulse.js
CHANGED
|
@@ -26,6 +26,8 @@ const decisao_autonoma_1 = require("./decisao-autonoma");
|
|
|
26
26
|
const docs_1 = require("./docs");
|
|
27
27
|
const conducao_1 = require("./conducao");
|
|
28
28
|
const conducao_texto_1 = require("./conducao-texto");
|
|
29
|
+
const hitl_resumo_1 = require("./hitl-resumo");
|
|
30
|
+
const parado_no_condutor_1 = require("./parado-no-condutor");
|
|
29
31
|
exports.CONTRATO_PULSE = 'ork.pulse/v1';
|
|
30
32
|
/** Uma semana: o resumo cita a decisao uma vez, e ela continua lida ate sair da janela. */
|
|
31
33
|
exports.JANELA_DAS_DECISOES_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -47,6 +49,17 @@ function apresentacaoCurtaDoItem(pedido, desde, quando) {
|
|
|
47
49
|
function comporPulse(carregado, entrada) {
|
|
48
50
|
const { radar, monitor, batch, orfas } = entrada, quando = radar.consultadoEm;
|
|
49
51
|
const humanos = [], automaticas = [];
|
|
52
|
+
// RM-037 (fatia 4): o fim de turno sem pergunta e do condutor. Ele sai da fila do dono aqui e volta
|
|
53
|
+
// como linha propria (`paradoNoCondutor`); leitura que falha nao esconde nada do dono.
|
|
54
|
+
let entregas = entrada.entregas;
|
|
55
|
+
if (!entregas) {
|
|
56
|
+
try {
|
|
57
|
+
entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
entregas = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
50
63
|
const registros = new Map();
|
|
51
64
|
const ehRegistro = (id) => {
|
|
52
65
|
if (!id)
|
|
@@ -79,6 +92,11 @@ function comporPulse(carregado, entrada) {
|
|
|
79
92
|
};
|
|
80
93
|
const TERMINAIS = ['failed', 'done', 'stopped', 'completed'];
|
|
81
94
|
for (const s of radar.sessoes.filter(s => s.precisaDeHumano)) {
|
|
95
|
+
// RM-037 (fatia 4): sessao `blocked` de quem o turno acabou sem pergunta e do condutor: a que o ledger provou
|
|
96
|
+
// (Stop sem atividade depois; a lista numerada da mensagem final nao e menu) e a sobra sem menu na tela.
|
|
97
|
+
if (entregas?.doCondutor.sessoes.has(s.sessionId) &&
|
|
98
|
+
((0, parado_no_condutor_1.sessaoSemPergunta)(s) || entregas.doCondutor.turnosEncerrados.has(s.sessionId)))
|
|
99
|
+
continue;
|
|
82
100
|
// Job morto ou estado terminal é história. Bloqueio vivo ou desconhecido continua visível.
|
|
83
101
|
if (ehRegistro(s.thread?.id ?? null) && s.jobVivo !== true &&
|
|
84
102
|
(s.jobVivo === false || TERMINAIS.includes(s.estadoBruto)))
|
|
@@ -123,6 +141,9 @@ function comporPulse(carregado, entrada) {
|
|
|
123
141
|
for (const p of l.paradas) {
|
|
124
142
|
if (VERIFICACAO.includes(p.motivo) && jaMesclada(l.thread))
|
|
125
143
|
continue;
|
|
144
|
+
// RM-037 (fatia 4): o `human.pending` que o observador grava no fim de turno sem pergunta nao e do dono.
|
|
145
|
+
if (p.motivo === 'human.pending' && p.fonte === 'ledger' && entregas?.doCondutor.gates.has(`${l.thread}|${p.fase}`))
|
|
146
|
+
continue;
|
|
126
147
|
if (ehRegistro(l.thread)) {
|
|
127
148
|
if (p.fonte === 'escalonador' || ['lease.busy', 'conducao.em-andamento', 'concurrency.limite', 'vaga.stale', 'runtime.silencio'].includes(p.motivo))
|
|
128
149
|
continue;
|
|
@@ -216,7 +237,8 @@ function comporPulse(carregado, entrada) {
|
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
239
|
return { contrato: exports.CONTRATO_PULSE, consultadoEm: quando, runtime: { ok: radar.runtimeConsultado, detalhe: radar.runtimeDetalhe },
|
|
219
|
-
precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length }
|
|
240
|
+
precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length },
|
|
241
|
+
...(entregas?.parados.length ? { paradoNoCondutor: entregas.parados } : {}) };
|
|
220
242
|
}
|
|
221
243
|
function montarPulse(carregado, opcoes = {}) {
|
|
222
244
|
if (opcoes.registrar)
|
|
@@ -243,7 +265,27 @@ function montarPulse(carregado, opcoes = {}) {
|
|
|
243
265
|
}
|
|
244
266
|
}
|
|
245
267
|
const monitor = (0, orquestracao_1.montarMonitor)(carregado, { agora: quando, estados });
|
|
246
|
-
|
|
268
|
+
// RM-037 (fatia 4, D2): a forja so e lida quando alguma thread tem branch publicada; a leitura boa vira o
|
|
269
|
+
// retrato que o status do roadmap le sem rede. Falha de leitura e diagnostico, nunca "sem PR".
|
|
270
|
+
let entregas;
|
|
271
|
+
try {
|
|
272
|
+
entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes, lerPrs: (candidatas) => {
|
|
273
|
+
const leitura = (0, parado_no_condutor_1.lerPrsDaForja)(carregado, { quando, executor: opcoes.executorDoGh, candidatas });
|
|
274
|
+
if (leitura.ok) {
|
|
275
|
+
try {
|
|
276
|
+
(0, parado_no_condutor_1.gravarRetratoDePrs)(carregado.raiz, leitura.retrato);
|
|
277
|
+
}
|
|
278
|
+
catch { /* o retrato e economia do status */ }
|
|
279
|
+
}
|
|
280
|
+
return leitura;
|
|
281
|
+
} });
|
|
282
|
+
if (entregas.prs && !entregas.prs.ok)
|
|
283
|
+
diagnosticos.push(`prs.nao-lidos: ${entregas.prs.erro.slice(0, 160)}`);
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
diagnosticos.push(`entregas.indisponiveis: ${e.message.slice(0, 120)}`);
|
|
287
|
+
}
|
|
288
|
+
const pulse = comporPulse(carregado, { radar, monitor, orfas, batch: (0, master_1.pendentesDeScore)(carregado.raiz), ...(entregas ? { entregas } : {}) });
|
|
247
289
|
const conducoes = monitor.linhas.filter((l) => l.conducao).map((l) => ({ thread: l.thread, linha: (0, conducao_texto_1.linhaDeConducao)(l.conducao, { agora: quando }) }));
|
|
248
290
|
if (conducoes.length > 0)
|
|
249
291
|
pulse.conducoes = conducoes;
|
|
@@ -264,8 +306,10 @@ function textoDoPulse(p) {
|
|
|
264
306
|
// I-35: o pulse guarda ISO; o texto ao dono sai no fuso dele, rotulado no cabeçalho.
|
|
265
307
|
const local = (texto) => (0, horario_1.localizarTexto)(texto, { agora: p.consultadoEm });
|
|
266
308
|
const linhas = [`Pulse (${p.contrato}) ${(0, horario_1.formatarDataHoraRotulada)(p.consultadoEm, { agora: p.consultadoEm })}`, `Precisa de humano agora: ${p.resumo.humanos}`, `${p.resumo.scores} entrega(s) sem nota, aceitas por padrão com registro`];
|
|
267
|
-
|
|
309
|
+
// RM-037 (fatia 4): PR nao lido tambem e dito, para a falta de linha do condutor nao parecer "nada parado".
|
|
310
|
+
if (p.runtime.ok && ['liveness.snapshot.invalid', 'prs.nao-lidos', 'entregas.indisponiveis'].some(d => p.runtime.detalhe.includes(d))) {
|
|
268
311
|
linhas.push(`[diagnostico] ${local(p.runtime.detalhe)}`);
|
|
312
|
+
}
|
|
269
313
|
if (!p.runtime.ok)
|
|
270
314
|
linhas.push(`[consulta incompleta] ${local(p.runtime.detalhe)}`);
|
|
271
315
|
for (const i of p.precisaDeHumanoAgora) {
|
|
@@ -276,6 +320,12 @@ function textoDoPulse(p) {
|
|
|
276
320
|
for (const c of p.conducoes)
|
|
277
321
|
linhas.push(`${c.thread}: ${c.linha}`);
|
|
278
322
|
}
|
|
323
|
+
// RM-037 (fatia 4): o que espera o condutor, uma linha por thread; nao e pergunta ao dono.
|
|
324
|
+
if (p.paradoNoCondutor?.length) {
|
|
325
|
+
linhas.push('', `Parado no condutor: ${p.paradoNoCondutor.length}`);
|
|
326
|
+
for (const x of p.paradoNoCondutor)
|
|
327
|
+
linhas.push((0, hitl_resumo_1.linhaDoParadoNoCondutor)(x, { agora: p.consultadoEm }));
|
|
328
|
+
}
|
|
279
329
|
linhas.push('', `Ações automáticas pendentes: ${p.resumo.automaticas}`);
|
|
280
330
|
for (const i of p.acoesAutomaticas)
|
|
281
331
|
linhas.push(`${i.thread}: ${i.motivo}; ${i.comandoResposta}`);
|