terminal-smart-cli 0.61.0 → 0.62.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/bin/ts.js +2 -2
- package/lib/agent.js +12 -4
- package/lib/core.js +54 -0
- package/lib/indice.js +0 -0
- package/lib/tools.js +1 -1
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -879,7 +879,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null } = {})
|
|
|
879
879
|
// HEADLESS stream-json: fecha com assistant (texto) + result (métricas) e sai.
|
|
880
880
|
if (streamJson) {
|
|
881
881
|
_emit(core.AgentEvents.assistant(out.text || ''));
|
|
882
|
-
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null, cwd: effCwd, duration_ms: Date.now() - t0, worktree: _wt ? { path: _wt.path, branch: _wt.branch, base: _wt.base, changed: (_wtInfo && _wtInfo.changed) || 0 } : null }));
|
|
882
|
+
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null, toolErrors: out.toolErrors || [], cwd: effCwd, duration_ms: Date.now() - t0, worktree: _wt ? { path: _wt.path, branch: _wt.branch, base: _wt.base, changed: (_wtInfo && _wtInfo.changed) || 0 } : null }));
|
|
883
883
|
return;
|
|
884
884
|
}
|
|
885
885
|
if (JSON_OUT) { console.log(JSON.stringify({ ok: true, result: out.text, steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context, needHuman: out.needHuman })); return; }
|
|
@@ -1619,7 +1619,7 @@ function buscarCmd(args) {
|
|
|
1619
1619
|
if (!hits.length) { console.log('\n' + ui.infoLine(en ? 'nothing relevant — try other terms' : 'nada relevante — tente outros termos')); return; }
|
|
1620
1620
|
console.log('\n' + ui.box([
|
|
1621
1621
|
C.bold(en ? 'Code search' : 'Busca no código') + C.dim(' "' + q.slice(0, 48) + '"'),
|
|
1622
|
-
...hits.flatMap(h => ['', C.cyan(h.file + ':' + h.l0 + '-' + h.l1) + C.dim(' ' + h.score),
|
|
1622
|
+
...hits.flatMap(h => ['', C.cyan(h.file + ':' + h.l0 + '-' + h.l1) + C.dim(' ' + h.score) + (h.sig && h.sig.length ? C.dim(' · define: ' + h.sig.slice(0, 4).join(', ')) : ''),
|
|
1623
1623
|
...h.snippet.split('\n').filter(l => l.trim()).slice(0, 2).map(l => C.dim(' ' + l.slice(0, 74)))]),
|
|
1624
1624
|
], { title: 'ts buscar' }));
|
|
1625
1625
|
}
|
package/lib/agent.js
CHANGED
|
@@ -355,6 +355,7 @@ async function run(task, opts = {}) {
|
|
|
355
355
|
const acc = { inTok: 0, outTok: 0, cachedTok: 0 };
|
|
356
356
|
const _mcpSeen = new Set(); // servidores MCP já autorizados NESTA sessão (1ª chamada pede OK)
|
|
357
357
|
const actions = []; // ações REAIS bem-sucedidas (evidência objetiva pro marcador do meta)
|
|
358
|
+
const _toolErrs = []; // erros de ferramenta TIPADOS na run (core.classifyToolResult) — sinal duro anti-done-falso
|
|
358
359
|
let finalText = '', usedModel = 'smart', steps = 0, charged = 0, _visionCredits = 0;
|
|
359
360
|
const ctxWindow = winFor(model);
|
|
360
361
|
// MEMÓRIA EPISÓDICA: ao fim da run, grava UM episódio (o que fez aqui) → a próxima run
|
|
@@ -649,9 +650,13 @@ async function run(task, opts = {}) {
|
|
|
649
650
|
onStep({ name, detail: argsShort(name, input) });
|
|
650
651
|
result = await tools.execute(name, input, { confineDir, baseDir: cwd, token });
|
|
651
652
|
steps++;
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
|
|
653
|
+
// TOOLRESULT TIPADO (core.classifyToolResult): classe de erro DETERMINÍSTICA. A verdade
|
|
654
|
+
// sobre "deu certo?" vem daqui, não da narrativa do modelo. 'blocked' = política (gate),
|
|
655
|
+
// não falha de execução — não conta pro ledger nem pro sinal de erro da run.
|
|
656
|
+
const _tr = core.classifyToolResult(result);
|
|
657
|
+
if (!_tr.ok && _tr.status !== 'blocked') {
|
|
658
|
+
_toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence });
|
|
659
|
+
// LEDGER DE ERROS (Evolve 3): registro determinístico, zero IA.
|
|
655
660
|
try { require('./memoria').logErro(confineDir || cwd, name, String(result.erro || result.stderr || ('exit ' + result.codigo))); } catch (_) {}
|
|
656
661
|
}
|
|
657
662
|
// HOOK PostToolUse (determinístico): feedback (ex: lint/format) vai pro modelo ver.
|
|
@@ -717,7 +722,10 @@ async function run(task, opts = {}) {
|
|
|
717
722
|
if (_hooks._any) { try { _hooksMod.run(_hooks, 'Stop', { cwd, text: finalText, steps }); } catch (_) {} }
|
|
718
723
|
_logEp(stopped ? 'cancel' : (loopedOut ? 'stuck' : 'done'));
|
|
719
724
|
await _bill();
|
|
720
|
-
|
|
725
|
+
// toolErrors: erros de ferramenta que NÃO foram seguidos de uma ação bem-sucedida da MESMA
|
|
726
|
+
// ferramenta depois (heurística leve de "não-recuperado") — sinal duro pro meta não marcar done falso.
|
|
727
|
+
const _lastErr = _toolErrs.length ? _toolErrs[_toolErrs.length - 1] : null;
|
|
728
|
+
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, messages, cwd, context: lastCtx, toolErrors: _toolErrs, lastToolError: _lastErr };
|
|
721
729
|
}
|
|
722
730
|
|
|
723
731
|
module.exports = { run, llm, _test: { winFor, estMsgsTok, COMPACT_AT, KEEP_TAIL, loopSig, isCycle, loopDecision } };
|
package/lib/core.js
CHANGED
|
@@ -74,8 +74,62 @@ const AgentEvents = {
|
|
|
74
74
|
// nomes dos eventos, pra quem for validar/mapear (App/Web)
|
|
75
75
|
const EVENT_TYPES = ['system', 'tool', 'assistant', 'result'];
|
|
76
76
|
|
|
77
|
+
// ── 4) TOOLRESULT TIPADO — classificação DETERMINÍSTICA (o modelo não decide sucesso) ──
|
|
78
|
+
// Insight das análises externas (GPT): "o modelo propõe; o harness autoriza, executa,
|
|
79
|
+
// observa e COMPROVA". Erro de ferramenta NUNCA pode virar etapa `done`. Aqui uma função
|
|
80
|
+
// PURA transforma o retorno bruto de qualquer ferramenta num envelope tipado sem alterar o
|
|
81
|
+
// raw (retrocompatível). Classe de erro alimenta o Recovery Engine e o ledger; `ok:false`
|
|
82
|
+
// deve barrar a marcação de sucesso a montante (meta/orquestrador).
|
|
83
|
+
const ERROR_CLASSES = ['permission_denied', 'not_found', 'port_in_use', 'missing_dep', 'package_lock',
|
|
84
|
+
'timeout', 'compile_error', 'test_failed', 'network', 'auth', 'git_conflict', 'invalid_schema',
|
|
85
|
+
'blocked', 'unknown'];
|
|
86
|
+
// classes transitórias → vale re-tentar (mesma abordagem); as demais exigem mudar de abordagem.
|
|
87
|
+
const RETRYABLE_CLASSES = new Set(['network', 'timeout', 'package_lock', 'port_in_use']);
|
|
88
|
+
|
|
89
|
+
function classifyError(msg) {
|
|
90
|
+
const s = String(msg || '').toLowerCase();
|
|
91
|
+
// POLÍTICA (nossos gates) — não é erro do mundo, é recusa: não re-tenta, não conta como falha de exec
|
|
92
|
+
if (/\bbloquead|\brecus|read-only|só-leitura|loop detectad|\bloop:|aprova(ção|r) neg|negou.*aprova/i.test(s)) return 'blocked';
|
|
93
|
+
if (/eacces|permission denied|access is denied|operation not permitted|acesso negado|permissão negada/i.test(s)) return 'permission_denied';
|
|
94
|
+
if (/could not get lock|dpkg was interrupted|resource temporarily unavailable.*lock|another (app|process) .*lock|unable to (acquire|lock)/i.test(s)) return 'package_lock';
|
|
95
|
+
if (/eaddrinuse|address already in use|porta .*(ocupad|em uso)|already running/i.test(s)) return 'port_in_use';
|
|
96
|
+
if (/cannot find module|command not found|not recognized as|modulenotfounderror|no module named|no such command|is not installed|não (foi )?encontrad[oa] o comando/i.test(s)) return 'missing_dep';
|
|
97
|
+
if (/enoent|no such file|não encontrad|not found|arquivo inexistente|404/i.test(s)) return 'not_found';
|
|
98
|
+
if (/etimedout|timed out|timeout|excedeu o tempo/i.test(s)) return 'timeout';
|
|
99
|
+
if (/econnrefused|enotfound|getaddrinfo|network|connection refused|conexão recusada|conn|dns/i.test(s)) return 'network';
|
|
100
|
+
if (/401|403|unauthorized|authentication failed|invalid credentials|credenciais inválidas|não autorizado/i.test(s)) return 'auth';
|
|
101
|
+
if (/merge conflict|\bconflict\b|needs merge|conflito de merge/i.test(s)) return 'git_conflict';
|
|
102
|
+
if (/syntaxerror|unexpected token|compile error|erro de compilação|error ts\d|cannot compile|parse error na compil/i.test(s)) return 'compile_error';
|
|
103
|
+
if (/\d+ (failing|failed)\b|test(s)? failed|assertion|expected .* (to|but)|falhou.*teste|teste(s)? falhar/i.test(s)) return 'test_failed';
|
|
104
|
+
if (/invalid json|json.*(parse|inválido)|schema|malformed/i.test(s)) return 'invalid_schema';
|
|
105
|
+
return 'unknown';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// raw = retorno de uma ferramenta (execute). Devolve { ok, status, errorClass, retryable, evidence }.
|
|
109
|
+
// NÃO muta o raw. Convenções do TS: {erro} = falha; {codigo|exitCode}≠0 = falha de shell;
|
|
110
|
+
// {written|created|resumo|stdout|...} = evidência de sucesso.
|
|
111
|
+
function classifyToolResult(raw) {
|
|
112
|
+
if (raw == null || typeof raw !== 'object') return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: raw != null ? String(raw).slice(0, 200) : '' };
|
|
113
|
+
const exit = raw.codigo != null ? raw.codigo : (raw.exitCode != null ? raw.exitCode : null);
|
|
114
|
+
const errMsg = raw.erro || raw.error || (exit != null && exit !== 0 ? (raw.stderr || raw.stdout || ('exit ' + exit)) : '');
|
|
115
|
+
const failed = !!raw.erro || !!raw.error || (exit != null && exit !== 0);
|
|
116
|
+
if (failed) {
|
|
117
|
+
const errorClass = classifyError(errMsg);
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
status: errorClass === 'blocked' ? 'blocked' : 'failed',
|
|
121
|
+
errorClass,
|
|
122
|
+
retryable: RETRYABLE_CLASSES.has(errorClass),
|
|
123
|
+
evidence: String(errMsg).replace(/\s+/g, ' ').slice(0, 300),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const ev = raw.written || raw.created || raw.resumo || raw.arquivo || (raw.stdout != null ? String(raw.stdout).slice(0, 200) : '') || (raw.total != null ? (raw.total + ' resultado(s)') : '');
|
|
127
|
+
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: String(ev || '').replace(/\s+/g, ' ').slice(0, 200) };
|
|
128
|
+
}
|
|
129
|
+
|
|
77
130
|
module.exports = {
|
|
78
131
|
DESTRUCTIVE, isDestructive,
|
|
79
132
|
MODEL_WINDOWS, CTX_WINDOW_DEFAULT, DEFAULT_EXECUTOR, COMPACT_AT, KEEP_TAIL, winFor, estMsgsTok,
|
|
80
133
|
AgentEvents, EVENT_TYPES,
|
|
134
|
+
ERROR_CLASSES, RETRYABLE_CLASSES, classifyError, classifyToolResult,
|
|
81
135
|
};
|
package/lib/indice.js
CHANGED
|
Binary file
|
package/lib/tools.js
CHANGED
|
@@ -522,7 +522,7 @@ async function execute(name, input, opts = {}) {
|
|
|
522
522
|
const k = Math.min(15, Math.max(1, Number(input.max) || 6));
|
|
523
523
|
const hits = ix.search(baseDir, consulta, k, idx);
|
|
524
524
|
if (!hits.length) return { resultados: [], aviso: 'nada relevante no índice — tente outros termos, ou rode "ts indexar" se o projeto mudou muito.' };
|
|
525
|
-
return { resultados: hits.map(h => ({ arquivo: h.file, linhas: h.l0 + '-' + h.l1, score: h.score, previa: h.snippet })), ...(construiu ? { nota: 'índice construído agora (' + idx.N + ' trechos)' } : {}) };
|
|
525
|
+
return { resultados: hits.map(h => ({ arquivo: h.file, linhas: h.l0 + '-' + h.l1, score: h.score, ...(h.sig && h.sig.length ? { define: h.sig } : {}), previa: h.snippet })), ...(construiu ? { nota: 'índice construído agora (' + idx.N + ' trechos)' } : {}) };
|
|
526
526
|
}
|
|
527
527
|
case 'buscar_web': {
|
|
528
528
|
const q = String(input.consulta || '').trim();
|