terminal-smart-cli 0.73.0 → 0.75.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/lib/core.js +14 -1
- package/lib/meta.js +20 -12
- package/lib/shared.js +55 -0
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -109,7 +109,20 @@ function classifyError(msg) {
|
|
|
109
109
|
// NÃO muta o raw. Convenções do TS: {erro} = falha; {codigo|exitCode}≠0 = falha de shell;
|
|
110
110
|
// {written|created|resumo|stdout|...} = evidência de sucesso.
|
|
111
111
|
function classifyToolResult(raw) {
|
|
112
|
-
if (raw == null
|
|
112
|
+
if (raw == null) return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: '' };
|
|
113
|
+
// Alguns adaptadores legados retornam falhas como texto. Tratar todo texto como sucesso
|
|
114
|
+
// permitia que "ERRO ao executar..." virasse etapa concluída. Mantém strings normais como
|
|
115
|
+
// sucesso, mas reconhece prefixos inequívocos de falha/bloqueio.
|
|
116
|
+
if (typeof raw !== 'object') {
|
|
117
|
+
const text = String(raw);
|
|
118
|
+
const failedText = /^\s*(?:(?:erro|error|falha|failed|bloquead[oa])(?:\s*[:\-]|\s+(?:ao|a|de|do|da|em|no|na|to|while|executing|calling|opening|reading|writing|request|operation)\b|\s*$)|operação cancelada\b|o usuário recusou\b)/i.test(text);
|
|
119
|
+
if (failedText) {
|
|
120
|
+
const errorClass = classifyError(text);
|
|
121
|
+
return { ok: false, status: errorClass === 'blocked' ? 'blocked' : 'failed', errorClass,
|
|
122
|
+
retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300) };
|
|
123
|
+
}
|
|
124
|
+
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200) };
|
|
125
|
+
}
|
|
113
126
|
const exit = raw.codigo != null ? raw.codigo : (raw.exitCode != null ? raw.exitCode : null);
|
|
114
127
|
const errMsg = raw.erro || raw.error || (exit != null && exit !== 0 ? (raw.stderr || raw.stdout || ('exit ' + exit)) : '');
|
|
115
128
|
const failed = !!raw.erro || !!raw.error || (exit != null && exit !== 0);
|
package/lib/meta.js
CHANGED
|
@@ -1011,7 +1011,8 @@ async function makeChecklist(goal, token) {
|
|
|
1011
1011
|
// CRITÉRIOS EXECUTÁVEIS (verify): a prova FINAL da missão. Rodados quando os gates de
|
|
1012
1012
|
// build/run passam, ANTES de declarar 'verified'. Se algum critério do usuário falha, a
|
|
1013
1013
|
// missão NÃO é verificada (honestidade — não basta compilar/abrir; tem que CUMPRIR o pedido).
|
|
1014
|
-
// Sem critérios
|
|
1014
|
+
// Sem critérios explícitos, os gates de build/run continuam sendo a prova. Erro interno do
|
|
1015
|
+
// verificador é fail-closed: nunca transforma ausência de prova em `verified`.
|
|
1015
1016
|
async function _checkCriteria(st, dir) {
|
|
1016
1017
|
if (!st.criteria || !st.criteria.length) return true;
|
|
1017
1018
|
try {
|
|
@@ -1019,7 +1020,11 @@ async function _checkCriteria(st, dir) {
|
|
|
1019
1020
|
st.verifyReport = { passed: rep.passed, total: rep.total, allOk: rep.allOk, results: rep.results.map(r => ({ ok: r.ok, label: r.label, detail: r.detail })) };
|
|
1020
1021
|
if (!rep.allOk) st.runNote = (st.runNote || '') + ` critérios: ${rep.passed}/${rep.total} passaram · falharam: ${rep.results.filter(r => !r.ok).map(r => r.label).slice(0, 3).join(' | ')}`;
|
|
1021
1022
|
return rep.allOk;
|
|
1022
|
-
} catch (
|
|
1023
|
+
} catch (e) {
|
|
1024
|
+
st.verifyReport = { passed: 0, total: st.criteria.length, allOk: false, results: [], error: String((e && e.message) || e).slice(0, 300) };
|
|
1025
|
+
st.runNote = (st.runNote || '') + ' verificador falhou internamente; missão não marcada como verified';
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1023
1028
|
}
|
|
1024
1029
|
|
|
1025
1030
|
async function run(goal, opts = {}) {
|
|
@@ -1064,12 +1069,14 @@ async function run(goal, opts = {}) {
|
|
|
1064
1069
|
}
|
|
1065
1070
|
const mk = await makeChecklist(goal + (designBrief ? '\n\n[Há um DESIGN SYSTEM definido — o checklist deve refletir a aplicação desse visual]' : '') + (archBrief ? '\n\n[Há um CONTRATO DE ARQUITETURA definido — os itens devem seguir os componentes/arquivos dele]' : ''), token);
|
|
1066
1071
|
// CRITÉRIOS (TaskSpec): usuário (--criterios) manda; senão, com --provar, combina o que o
|
|
1067
|
-
// PLANNER propôs (mk.criteria, já validado) + os TESTES da stack — tudo recompilado.
|
|
1068
|
-
// --provar,
|
|
1072
|
+
// PLANNER propôs (mk.criteria, já validado) + os TESTES da stack — tudo recompilado.
|
|
1073
|
+
// Mesmo sem --provar, critérios determinísticos derivados da stack rodam por padrão;
|
|
1074
|
+
// critérios inventados pelo planner ficam apenas como referência até o usuário pedir prova ampla.
|
|
1069
1075
|
const _V = require('./verify');
|
|
1070
1076
|
let _criteria = [], _planned = mk.criteria || [];
|
|
1071
1077
|
if (Array.isArray(opts.criteria) && opts.criteria.length) _criteria = opts.criteria;
|
|
1072
1078
|
else if (prove) _criteria = _V.compileCriteria([..._V.deriveFromStack(dir), ..._planned]).criteria.slice(0, 8);
|
|
1079
|
+
else _criteria = _V.compileCriteria(_V.deriveFromStack(dir)).criteria.slice(0, 8);
|
|
1073
1080
|
st = { goal: String(goal).slice(0, 4000), mockup: opts.mockup || null, archBrief, feasBrief, checklist: mk.itens, rounds: [], creditsSpent: (mk.credits || 0) + designCred + archCred + feasCred,
|
|
1074
1081
|
budget, maxRounds, status: 'running', model: model || null, designBrief: designBrief || null, criteria: _criteria, plannedCriteria: _planned, created_at: new Date().toISOString() };
|
|
1075
1082
|
save(st, dir);
|
|
@@ -1346,14 +1353,16 @@ async function run(goal, opts = {}) {
|
|
|
1346
1353
|
// (Bug real do teste MultiApps: flash-lite bloqueou itens cujos arquivos existiam no disco.)
|
|
1347
1354
|
let marks = [];
|
|
1348
1355
|
const written = (out.actions || []).filter(a => a.name === 'escrever_arquivo' || a.name === 'editar_arquivo').map(a => String(a.target || ''));
|
|
1349
|
-
const ranOk = (out.actions || []).some(a => a.name === 'executar_comando');
|
|
1350
1356
|
const _basename = (p) => String(p).replace(/\\/g, '/').split('/').pop().toLowerCase();
|
|
1351
1357
|
const _fileHints = (txt) => (String(txt).match(/[\w.\-]+\.(kt|java|xml|gradle|json|md|txt|kts|properties|pro|png|webp|py|js|ts|html|css|sh)\b/gi) || []).map(s => s.toLowerCase());
|
|
1352
1358
|
const itemFiles = _fileHints(item.desc);
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1359
|
+
// Uma escrita/comando genérico não prova um item sem vínculo. Só há marcação automática
|
|
1360
|
+
// quando o próprio texto do item identifica o artefato e a rodada gravou esse artefato.
|
|
1361
|
+
// Itens abstratos continuam podendo ser avaliados pelo marcador, mas não "vazam" sucesso
|
|
1362
|
+
// para outros itens só porque algum arquivo/comando apareceu na mesma rodada.
|
|
1363
|
+
const wroteForItem = itemFiles.length > 0
|
|
1364
|
+
&& itemFiles.some(f => written.some(w => _basename(w) === f));
|
|
1365
|
+
if (wroteForItem) marks.push(item.id);
|
|
1357
1366
|
|
|
1358
1367
|
// Item de correção de build: NÃO usa marcador — o PORTÃO DE BUILD é a única
|
|
1359
1368
|
// autoridade (recompila de verdade). Marca provisório pra reabrir o gate; se o
|
|
@@ -1368,15 +1377,14 @@ async function run(goal, opts = {}) {
|
|
|
1368
1377
|
continue;
|
|
1369
1378
|
}
|
|
1370
1379
|
|
|
1371
|
-
// Marcador IA como REFORÇO
|
|
1372
|
-
//
|
|
1380
|
+
// Marcador IA como REFORÇO do ITEM ALVO. Não aceita mais `tambem_concluidos`: uma
|
|
1381
|
+
// narrativa de uma rodada não é evidência suficiente para concluir itens não executados.
|
|
1373
1382
|
try {
|
|
1374
1383
|
const evid = written.length ? '\n\nARQUIVOS REALMENTE GRAVADOS nesta rodada (evidência objetiva):\n' + written.map(_basename).join(', ') : '';
|
|
1375
1384
|
const mk = await _llmJson({ token, system: MARK_SYS,
|
|
1376
1385
|
user: `OBJETIVO:\n${st.goal.slice(0, 600)}\n\nCHECKLIST (contexto):\n${fmtChecklist(st.checklist)}\n\nITEM ALVO: (${item.id}) ${item.desc}\n\nRESULTADO DA RODADA:\n${String(out.text || '').slice(0, 2000)}${evid}` });
|
|
1377
1386
|
st.creditsSpent += mk.credits || 0;
|
|
1378
1387
|
if (mk.json && mk.json.passou === true) marks.push(item.id);
|
|
1379
|
-
if (Array.isArray(mk.json?.tambem_concluidos)) marks.push(...mk.json.tambem_concluidos.map(String));
|
|
1380
1388
|
} catch (_) {}
|
|
1381
1389
|
marks = [...new Set(marks)];
|
|
1382
1390
|
for (const it of st.checklist) if (marks.includes(it.id)) it.passes = true;
|
package/lib/shared.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ⌁ NÚCLEO CANÔNICO PORTÁVEL do Terminal Smart — o "@terminal-smart/agent-core" mínimo.
|
|
2
|
+
//
|
|
3
|
+
// Convergência F4 (análises GPT): a Web e o App reimplementavam de forma divergente a mesma
|
|
4
|
+
// lógica de gate/erro/verificação → risco de comportamento inconsistente entre superfícies
|
|
5
|
+
// (um comando bloqueado no CLI e liberado no App). Este ponto de entrada reúne o subconjunto
|
|
6
|
+
// do motor que NÃO depende do CLI (nada de api/ui/config/agent/tools) — só Node builtins e
|
|
7
|
+
// estes 4 módulos — pra QUALQUER superfície consumir a MESMA fonte de verdade.
|
|
8
|
+
//
|
|
9
|
+
// Como a Web/App consomem: copiar core.js + verify.js + recovery.js + stack.js + shared.js
|
|
10
|
+
// para o backend (ou publicar como pacote npm e depender dele). ZERO mudança de runtime.
|
|
11
|
+
//
|
|
12
|
+
// Garantia: test/core.test.js tem um GUARDA que falha se qualquer um destes módulos passar a
|
|
13
|
+
// importar algo específico do CLI — mantendo o núcleo portável para sempre.
|
|
14
|
+
'use strict';
|
|
15
|
+
const core = require('./core');
|
|
16
|
+
const verify = require('./verify');
|
|
17
|
+
const recovery = require('./recovery');
|
|
18
|
+
const stack = require('./stack');
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
// namespaces completos
|
|
22
|
+
core, verify, recovery, stack,
|
|
23
|
+
|
|
24
|
+
// ── atalhos das capacidades mais usadas (a superfície importa daqui) ──
|
|
25
|
+
// Gate de destrutivo (fonte única — App/Web param de ter regex divergente)
|
|
26
|
+
isDestructive: core.isDestructive,
|
|
27
|
+
DESTRUCTIVE: core.DESTRUCTIVE,
|
|
28
|
+
// ToolResult tipado + classes de erro (erro nunca vira "concluído")
|
|
29
|
+
classifyToolResult: core.classifyToolResult,
|
|
30
|
+
classifyError: core.classifyError,
|
|
31
|
+
ERROR_CLASSES: core.ERROR_CLASSES,
|
|
32
|
+
RETRYABLE_CLASSES: core.RETRYABLE_CLASSES,
|
|
33
|
+
// Janela de contexto / compactação
|
|
34
|
+
winFor: core.winFor,
|
|
35
|
+
estMsgsTok: core.estMsgsTok,
|
|
36
|
+
COMPACT_AT: core.COMPACT_AT,
|
|
37
|
+
KEEP_TAIL: core.KEEP_TAIL,
|
|
38
|
+
DEFAULT_EXECUTOR: core.DEFAULT_EXECUTOR,
|
|
39
|
+
// Envelope de eventos (contrato stream-json único)
|
|
40
|
+
AgentEvents: core.AgentEvents,
|
|
41
|
+
EVENT_TYPES: core.EVENT_TYPES,
|
|
42
|
+
// Verificadores + TaskSpec (o modelo propõe, o harness valida e prova)
|
|
43
|
+
runCriteria: verify.runAll,
|
|
44
|
+
runCriterion: verify.runCriterion,
|
|
45
|
+
compileCriteria: verify.compileCriteria,
|
|
46
|
+
deriveFromStack: verify.deriveFromStack,
|
|
47
|
+
// Recovery Engine (estratégia de conserto por classe de erro)
|
|
48
|
+
recoveryHint: recovery.recoveryHint,
|
|
49
|
+
strategiesFor: recovery.strategiesFor,
|
|
50
|
+
// Detector de stack
|
|
51
|
+
detectStack: stack.detectStack,
|
|
52
|
+
|
|
53
|
+
// versão do contrato (bump quando a forma dos exports/eventos mudar)
|
|
54
|
+
CORE_CONTRACT: 1,
|
|
55
|
+
};
|