terminal-smart-cli 0.61.0 → 0.63.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 +57 -6
- package/lib/agent.js +12 -4
- package/lib/core.js +54 -0
- package/lib/diagnose.js +3 -3
- package/lib/i18n.js +1 -1
- package/lib/indice.js +0 -0
- package/lib/sentinela.js +2 -1
- 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; }
|
|
@@ -1432,13 +1432,14 @@ async function sentinelaCmd(args) {
|
|
|
1432
1432
|
regra: _regraFromFlags(),
|
|
1433
1433
|
avisar: _val(['--avisar', '--notify']) || 'falha',
|
|
1434
1434
|
escalar: FLAGS.has('--escalar') || FLAGS.has('--escalate'),
|
|
1435
|
+
autofix: FLAGS.has('--autofix') || FLAGS.has('--auto-fix'),
|
|
1435
1436
|
});
|
|
1436
1437
|
console.log('\n' + ui.box([
|
|
1437
1438
|
C.ok(en ? 'Sentinel added' : 'Sentinela adicionada') + C.dim(' ' + check.id),
|
|
1438
1439
|
'',
|
|
1439
1440
|
C.bold(check.nome) + C.dim(' ' + (check.target ? check.target.split(' ').pop() : (en ? 'local' : 'local'))),
|
|
1440
1441
|
C.dim('$ ') + check.cmd.slice(0, 70),
|
|
1441
|
-
C.dim((en ? 'rule: ' : 'regra: ') + _regraTxt(check.regra) + (en ? ' · every ' : ' · a cada ') + (check.cron || sen.parseEvery(_val(['--cada']) || '').ms / 60000 + 'm') + (check.escalar ? C.warn(' · escala p/ IA se falhar') : '')),
|
|
1442
|
+
C.dim((en ? 'rule: ' : 'regra: ') + _regraTxt(check.regra) + (en ? ' · every ' : ' · a cada ') + (check.cron || sen.parseEvery(_val(['--cada']) || '').ms / 60000 + 'm') + (check.autofix ? C.warn(' · 🔧 autofix (conserta c/ aprovação)') : check.escalar ? C.warn(' · escala p/ IA se falhar') : '')),
|
|
1442
1443
|
], { title: 'ts sentinela' }));
|
|
1443
1444
|
console.log('\n' + C.dim(en ? 'Activate the schedule with: ' : 'Ative o agendamento com: ') + 'ts sentinela instalar');
|
|
1444
1445
|
return;
|
|
@@ -1452,7 +1453,7 @@ async function sentinelaCmd(args) {
|
|
|
1452
1453
|
C.bold(en ? 'Sentinels' : 'Sentinelas') + C.dim(' (' + list.length + ')'),
|
|
1453
1454
|
...list.flatMap(c => [
|
|
1454
1455
|
'',
|
|
1455
|
-
_icon(!c.ultimo || c.ultimo.ok) + ' ' + C.bold(c.nome) + C.dim(' ' + c.id + (c.escalar ? ' ⚡' : '') + (c.target ? ' ' + c.target.split(' ').pop() : '')),
|
|
1456
|
+
_icon(!c.ultimo || c.ultimo.ok) + ' ' + C.bold(c.nome) + C.dim(' ' + c.id + (c.autofix ? ' 🔧' : c.escalar ? ' ⚡' : '') + (c.target ? ' ' + c.target.split(' ').pop() : '')),
|
|
1456
1457
|
C.dim(' $ ' + c.cmd.slice(0, 66)),
|
|
1457
1458
|
C.dim(' ' + _regraTxt(c.regra) + ' · ' + (c.cron || ((c.every_ms / 60000) + 'm')) + (c.ultimo ? ' · ' + (en ? 'last: ' : 'último: ') + (c.ultimo.ok ? C.ok('ok') : C.err(c.ultimo.motivo)) : ' · ' + (en ? 'never run' : 'nunca rodou'))),
|
|
1458
1459
|
]),
|
|
@@ -1493,17 +1494,24 @@ async function sentinelaCmd(args) {
|
|
|
1493
1494
|
const out = ((r.res.stdout || '') + (r.res.stderr || '')).trim();
|
|
1494
1495
|
if (out && !r.verdict.ok) msg += '\n' + out.split('\n').slice(0, 6).join('\n').slice(0, 500);
|
|
1495
1496
|
// ESCALA pra IA só quando falha e --escalar (aqui nasce a sugestão de conserto)
|
|
1497
|
+
let diagResult = null;
|
|
1496
1498
|
if (!r.verdict.ok && check.escalar && token) {
|
|
1497
1499
|
try {
|
|
1498
1500
|
const diag = require('../lib/diagnose');
|
|
1499
|
-
|
|
1501
|
+
diagResult = await diag.diagnose(`sentinela "${check.nome}" falhou: ${r.verdict.motivo}. comando: ${check.cmd}. saída: ${out.slice(0, 400)}`, {
|
|
1500
1502
|
token, lang: cfg.lang || 'pt', target: check.target || '', maxRounds: 4, onEvent: () => {},
|
|
1501
1503
|
});
|
|
1502
|
-
if (
|
|
1504
|
+
if (diagResult.status === 'solved') msg += '\n\n🧠 causa provável: ' + String(diagResult.rootCause).slice(0, 200) + '\n🔧 conserto: ' + String(diagResult.fix || '—').slice(0, 240);
|
|
1503
1505
|
} catch (_) { /* diagnose best-effort */ }
|
|
1504
1506
|
}
|
|
1505
1507
|
if (token) { try { await metaMod.notify(token, msg); } catch (_) {} }
|
|
1506
1508
|
if (!quiet) console.log(msg);
|
|
1509
|
+
// ── AUTOFIX (Incident Autopilot): conserto verificado + comando seguro → aprovação no Telegram → aplica → re-valida.
|
|
1510
|
+
// NUNCA aplica sem OK humano (aprovação remota). Só quando o diagnóstico sobreviveu à refutação.
|
|
1511
|
+
if (!r.verdict.ok && check.autofix && token && diagResult && diagResult.status === 'solved' && diagResult.verified && diagResult.fixCommand) {
|
|
1512
|
+
const applied = await _sentinelaAutofix(check, diagResult, r, token, quiet, en);
|
|
1513
|
+
if (applied != null) alerts++;
|
|
1514
|
+
}
|
|
1507
1515
|
}
|
|
1508
1516
|
sen._save(list);
|
|
1509
1517
|
if (!quiet) console.log(C.dim(`\n${en ? 'ran' : 'rodou'} ${ran} · ${alerts} ${en ? 'alert(s)' : 'alerta(s)'}`));
|
|
@@ -1556,6 +1564,49 @@ async function sentinelaCmd(args) {
|
|
|
1556
1564
|
}
|
|
1557
1565
|
function execSyncQuiet(c) { return require('child_process').execSync(c, { stdio: 'ignore', windowsHide: true }); }
|
|
1558
1566
|
|
|
1567
|
+
// AUTOFIX da sentinela (Incident Autopilot): propõe o conserto no Telegram, e SÓ aplica com
|
|
1568
|
+
// aprovação humana. Aplica no alvo (local ou ssh remoto), re-valida o check e reporta. Retorna
|
|
1569
|
+
// true/false se aplicou (consertou ou não), null se não houve aprovação/canal.
|
|
1570
|
+
async function _sentinelaAutofix(check, diag, r, token, quiet, en) {
|
|
1571
|
+
const sen = require('../lib/sentinela');
|
|
1572
|
+
const metaMod = require('../lib/meta');
|
|
1573
|
+
const cmd = String(diag.fixCommand).trim();
|
|
1574
|
+
const alvo = check.target ? check.target.split(' ').pop() : os.hostname();
|
|
1575
|
+
// 1) aprovação remota (mesmo canal do agente): /api/cli/approval/start → poll
|
|
1576
|
+
let st = null;
|
|
1577
|
+
try { st = await api('/api/cli/approval/start', { method: 'POST', token, body: { comando: `[sentinela ${check.nome}@${alvo}] ${cmd}`, host: alvo }, timeoutMs: 20000 }); } catch (_) { return null; }
|
|
1578
|
+
if (!st || !st.success || !st.id) { // sem canal Telegram → só propõe e para
|
|
1579
|
+
if (!quiet) console.log(' ' + C.dim(en ? 'autofix: no Telegram channel to approve — proposed only.' : 'autofix: sem canal Telegram pra aprovar — só proposto.'));
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
if (!quiet) console.log(' ' + C.warn('🔧 ') + C.dim((en ? 'autofix: sent to Telegram for approval — ' : 'autofix: enviei pro Telegram pra aprovar — ') + cmd.slice(0, 60)));
|
|
1583
|
+
let approved = false;
|
|
1584
|
+
const t0 = Date.now();
|
|
1585
|
+
while (Date.now() - t0 < 150000) {
|
|
1586
|
+
await new Promise(rs => setTimeout(rs, 3000));
|
|
1587
|
+
let p = null;
|
|
1588
|
+
try { p = await api('/api/cli/approval/poll', { method: 'POST', token, body: { id: st.id }, timeoutMs: 15000 }); } catch (_) { continue; }
|
|
1589
|
+
if (p && p.status === 'approved') { approved = true; break; }
|
|
1590
|
+
if (p && (p.status === 'denied' || p.status === 'expired')) break;
|
|
1591
|
+
}
|
|
1592
|
+
if (!approved) {
|
|
1593
|
+
try { await metaMod.notify(token, `⏹️ [sentinela] ${check.nome}: conserto NÃO aplicado (não aprovado). Comando proposto: ${cmd}`); } catch (_) {}
|
|
1594
|
+
if (!quiet) console.log(' ' + C.dim(en ? 'autofix: not approved — nothing applied.' : 'autofix: não aprovado — nada aplicado.'));
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
// 2) aplica no alvo + 3) re-valida o check
|
|
1598
|
+
const ap = sen.runShell(cmd, { target: check.target, timeoutMs: 60000 });
|
|
1599
|
+
const rr = sen.runOne(check); // re-roda o check → atualiza estado
|
|
1600
|
+
const fixedOk = rr.verdict.ok;
|
|
1601
|
+
const rerun = ((rr.res.stdout || '') + (rr.res.stderr || '')).trim().split('\n').slice(0, 4).join('\n').slice(0, 300);
|
|
1602
|
+
const finalMsg = fixedOk
|
|
1603
|
+
? `✅ [sentinela] ${check.nome}: CONSERTADO por autofix. Apliquei \`${cmd}\` e o check voltou a passar.`
|
|
1604
|
+
: `⚠️ [sentinela] ${check.nome}: autofix aplicado (\`${cmd}\`) mas o check AINDA falha (${rr.verdict.motivo}). Precisa de você.\n${rerun}`;
|
|
1605
|
+
try { await metaMod.notify(token, finalMsg); } catch (_) {}
|
|
1606
|
+
if (!quiet) console.log(' ' + (fixedOk ? C.ok('✔ ') + (en ? 'autofix worked — check passes again' : 'autofix funcionou — check voltou a passar') : C.err('✗ ') + (en ? 'applied but still failing' : 'aplicado mas ainda falha')) + C.dim((ap.code !== 0 ? ' (fix exit ' + ap.code + ')' : '')));
|
|
1607
|
+
return fixedOk;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1559
1610
|
// ── ts sonhar — DREAM SESSION: consolida a memória episódica em camadas ───────
|
|
1560
1611
|
async function sonharCmd(args) {
|
|
1561
1612
|
const en = (cfg.lang === 'en');
|
|
@@ -1619,7 +1670,7 @@ function buscarCmd(args) {
|
|
|
1619
1670
|
if (!hits.length) { console.log('\n' + ui.infoLine(en ? 'nothing relevant — try other terms' : 'nada relevante — tente outros termos')); return; }
|
|
1620
1671
|
console.log('\n' + ui.box([
|
|
1621
1672
|
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),
|
|
1673
|
+
...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
1674
|
...h.snippet.split('\n').filter(l => l.trim()).slice(0, 2).map(l => C.dim(' ' + l.slice(0, 74)))]),
|
|
1624
1675
|
], { title: 'ts buscar' }));
|
|
1625
1676
|
}
|
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/diagnose.js
CHANGED
|
@@ -25,13 +25,13 @@ ${where}
|
|
|
25
25
|
METHOD each round: pick your single strongest HYPOTHESIS and ONE cheap READ-ONLY probe that would CONFIRM or REFUTE it. Read the probe output next round, then keep or discard the hypothesis and move on. Rank likely causes first (recent changes, environment, config, permissions, resources, data/version, then logic).
|
|
26
26
|
HARD RULES: (1) probe must be READ-ONLY (ldd, strace -f, cat, head, tail, ls, stat, file, readelf, md5sum, ss, ps, grep, find, ldconfig -p, dpkg -l, journalctl, "mysql ... SELECT/SHOW"). NEVER install/rm/mv/chmod/restart/write. (2) Don't repeat a probe already run. (3) A missing library that "exists" often means its OWN dependency is missing — probe the lib itself. (4) Distinguish ENVIRONMENT (missing lib, perm, port, config) from DATA/INTERNAL (version mismatch, corrupt/incompatible file, logic). (5) When the evidence proves the cause, STOP and report it with a concrete fix.
|
|
27
27
|
Reply ONLY with a JSON object, no prose:
|
|
28
|
-
{"thought":"1-2 sentences reasoning from the latest evidence","status":"investigating|solved|stuck","hypothesis":"current leading hypothesis (short)","category":"environment|config|permission|resource|network|data|version|logic|unknown","probe":"ONE read-only shell command (empty if solved/stuck)","expect_if_true":"what in the output confirms it","root_cause":"(only if solved) the confirmed root cause","fix":"(only if solved) the concrete fix — commands/steps","confidence":0}`
|
|
28
|
+
{"thought":"1-2 sentences reasoning from the latest evidence","status":"investigating|solved|stuck","hypothesis":"current leading hypothesis (short)","category":"environment|config|permission|resource|network|data|version|logic|unknown","probe":"ONE read-only shell command (empty if solved/stuck)","expect_if_true":"what in the output confirms it","root_cause":"(only if solved) the confirmed root cause","fix":"(only if solved) the concrete fix — commands/steps","fix_command":"(only if solved AND the fix is ONE safe idempotent shell command that applies it — e.g. restart a service, free a port, install a dep; empty if it needs code edits/multiple steps/anything risky)","confidence":0}`
|
|
29
29
|
: `Você é um DIAGNOSTICADOR SÊNIOR. Dado um sintoma, ache a CAUSA RAIZ investigando — nunca chute o conserto às cegas.
|
|
30
30
|
${where}
|
|
31
31
|
MÉTODO por rodada: escolha sua HIPÓTESE mais forte e UMA sonda barata SÓ-LEITURA que a CONFIRME ou REFUTE. Leia a saída na rodada seguinte, mantenha ou descarte a hipótese e siga. Rankeie causas prováveis primeiro (mudanças recentes, ambiente, config, permissões, recursos, dado/versão, e por fim lógica).
|
|
32
32
|
REGRAS DURAS: (1) a sonda tem que ser SÓ-LEITURA (ldd, strace -f, cat, head, tail, ls, stat, file, readelf, md5sum, ss, ps, grep, find, ldconfig -p, dpkg -l, journalctl, "mysql ... SELECT/SHOW"). NUNCA install/rm/mv/chmod/restart/escrever. (2) Não repita uma sonda já feita. (3) Biblioteca "não encontrada" que EXISTE quase sempre é DEPENDÊNCIA-DA-DEPENDÊNCIA faltando — sonde a própria lib. (4) Distinga AMBIENTE (lib faltando, permissão, porta, config) de DADO/INTERNO (mismatch de versão, arquivo corrompido/incompatível, lógica). (5) Quando a evidência provar a causa, PARE e reporte com um conserto concreto.
|
|
33
33
|
Responda SÓ com um objeto JSON, sem prosa:
|
|
34
|
-
{"thought":"raciocínio de 1-2 frases a partir da última evidência","status":"investigating|solved|stuck","hypothesis":"hipótese atual (curta)","category":"environment|config|permission|resource|network|data|version|logic|unknown","probe":"UM comando shell só-leitura (vazio se solved/stuck)","expect_if_true":"o que na saída confirma","root_cause":"(só se solved) a causa raiz confirmada","fix":"(só se solved) o conserto concreto — comandos/passos","confidence":0}`;
|
|
34
|
+
{"thought":"raciocínio de 1-2 frases a partir da última evidência","status":"investigating|solved|stuck","hypothesis":"hipótese atual (curta)","category":"environment|config|permission|resource|network|data|version|logic|unknown","probe":"UM comando shell só-leitura (vazio se solved/stuck)","expect_if_true":"o que na saída confirma","root_cause":"(só se solved) a causa raiz confirmada","fix":"(só se solved) o conserto concreto — comandos/passos","fix_command":"(só se solved E o conserto for UM comando shell seguro e idempotente que o aplica — ex.: reiniciar um serviço, liberar uma porta, instalar uma dep; vazio se exigir edição de código/vários passos/qualquer coisa arriscada)","confidence":0}`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function extractJson(s) {
|
|
@@ -95,7 +95,7 @@ async function diagnose(symptom, opts = {}) {
|
|
|
95
95
|
continue; // a própria conclusão caiu na verificação → volta a investigar
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
result = { status: 'solved', rootCause: claim, fix: j.fix || '', category: j.category || 'unknown', confidence: j.confidence || 0, verified: !!rprobe, evidence, rounds: round };
|
|
98
|
+
result = { status: 'solved', rootCause: claim, fix: j.fix || '', fixCommand: String(j.fix_command || '').trim(), category: j.category || 'unknown', confidence: j.confidence || 0, verified: !!rprobe, evidence, rounds: round };
|
|
99
99
|
onEvent({ type: 'solved', rootCause: result.rootCause, fix: result.fix, category: result.category, verified: result.verified });
|
|
100
100
|
break;
|
|
101
101
|
}
|
package/lib/i18n.js
CHANGED
|
@@ -38,7 +38,7 @@ const STR = {
|
|
|
38
38
|
['ts agente "..." --yes', 'autônomo (destrutivo pede aprovação no Telegram)'],
|
|
39
39
|
['ts diagnosticar "erro"', 'INVESTIGA a causa raiz: hipótese→sonda→veredito (só-leitura)'],
|
|
40
40
|
['ts diagnosticar "..." --remoto "ssh user@host"', 'investiga uma máquina remota'],
|
|
41
|
-
['ts sentinela add "nome" --cmd "..."', 'VIGIA determinístico (regra do if, ZERO token);
|
|
41
|
+
['ts sentinela add "nome" --cmd "..."', 'VIGIA determinístico (regra do if, ZERO token); --escalar diagnostica se falhar; --autofix conserta (com aprovação no Telegram)'],
|
|
42
42
|
['ts sentinela instalar', 'agenda os checks no SO (cron/Task Scheduler) e avisa no Telegram'],
|
|
43
43
|
['ts agente --continuar "..."', 'retoma o trabalho anterior desta pasta'],
|
|
44
44
|
['ts agente "..." --navegador', 'EXPERIMENTAL: dá um Chrome real ao agente (abrir/ler/clicar/print+visão)'],
|
package/lib/indice.js
CHANGED
|
Binary file
|
package/lib/sentinela.js
CHANGED
|
@@ -91,7 +91,8 @@ function add(def) {
|
|
|
91
91
|
every_ms: ev.ms, cron: ev.cron,
|
|
92
92
|
regra: def.regra || { tipo: 'exit0' },
|
|
93
93
|
avisar: def.avisar || 'falha', // 'falha' | 'sempre' | 'mudanca'
|
|
94
|
-
escalar: !!def.escalar,
|
|
94
|
+
escalar: !!def.escalar || !!def.autofix, // escala pro diagnose quando falha (autofix implica escalar)
|
|
95
|
+
autofix: !!def.autofix, // propõe o conserto no Telegram (aprovação) e re-valida
|
|
95
96
|
criado: Date.now(),
|
|
96
97
|
proximo: ev.ms ? Date.now() : 0, // cron: 0 = decide por horário
|
|
97
98
|
ultimo: null, // { ts, ok, motivo, hash }
|
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();
|