terminal-smart-cli 0.91.1 → 0.92.2
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 +13 -5
- package/lib/ssh.js +16 -4
- package/lib/tools.js +8 -1
- package/package.json +1 -1
package/bin/ts.js
CHANGED
|
@@ -913,8 +913,13 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
913
913
|
// saída → o log ficava VAZIO enquanto o agente rodava. Nesses casos usa spinner NOOP: os passos
|
|
914
914
|
// saem por console.log (visíveis no arquivo/pipe) e o progresso aparece de verdade.
|
|
915
915
|
const _noTTY = !process.stdout.isTTY;
|
|
916
|
+
// B4: headless (não-TTY, redirect >arquivo/pipe) — o console.log é bufferizado em BLOCO e o
|
|
917
|
+
// log fica VAZIO durante a run (impossível acompanhar ao vivo; foi o que escondeu o hang de
|
|
918
|
+
// 40min no PW). _hlog escreve SÍNCRONO no fd 1 (fs.writeSync), descarregando cada linha na
|
|
919
|
+
// hora. No TTY segue o console.log normal (o spinner cuida do redesenho).
|
|
920
|
+
const _hlog = (s) => { if (_noTTY && !streamJson) { try { require('fs').writeSync(1, s + '\n'); return; } catch (_) {} } console.log(s); };
|
|
916
921
|
const sp = (streamJson || _noTTY) ? { text() {}, start() {}, stop() {} } : ui.spinner(T.agent_thinking).start();
|
|
917
|
-
if (_noTTY && !streamJson)
|
|
922
|
+
if (_noTTY && !streamJson) _hlog(' ' + C.dim(T.agent_thinking));
|
|
918
923
|
const t0 = Date.now();
|
|
919
924
|
// STATUS LINE ao vivo (estilo Claude/Verboo): tempo · modelo · tokens · passo — atualiza a cada 1s.
|
|
920
925
|
const _live = { steps: 0, inTok: 0, outTok: 0, model: model || 'auto' };
|
|
@@ -950,13 +955,13 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
950
955
|
const tag = auto ? C.warn('▲ auto') : retry ? C.warn('⟳') : loop ? C.warn('↻ loop') : blocked ? C.err('■ ' + T.agent_blocked) : C.cyan('›');
|
|
951
956
|
// nome AMIGÁVEL (Rodando no servidor…) em vez do técnico (executar_remoto); detalhe sem cortar palavra
|
|
952
957
|
const _label = ui.friendlyTool(name, cfg.lang === 'en');
|
|
953
|
-
|
|
958
|
+
_hlog(' ' + tag + ' ' + C.bold(_label) + (detail ? C.dim(' · ' + ui.wordTrunc(detail, 60)) : ''));
|
|
954
959
|
sp.start();
|
|
955
960
|
},
|
|
956
961
|
onStepDone: (e) => {
|
|
957
962
|
if (streamJson) return; // o evento 'result' já cobre; aqui é só a UI bonita
|
|
958
963
|
sp.stop();
|
|
959
|
-
|
|
964
|
+
_hlog(stepDoneLine(e, ' '));
|
|
960
965
|
sp.start();
|
|
961
966
|
},
|
|
962
967
|
askApprove: async (payload) => {
|
|
@@ -970,7 +975,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
970
975
|
onRemote: ({ ttl }) => {
|
|
971
976
|
if (streamJson) { _emit(core.AgentEvents.tool({ subtype: 'approval_remote', ttl: ttl || 120 })); return; }
|
|
972
977
|
sp.stop();
|
|
973
|
-
|
|
978
|
+
_hlog(' ' + C.warn('▲') + ' ' + C.dim(T.agent_remote_wait(ttl || 120)));
|
|
974
979
|
sp.start();
|
|
975
980
|
},
|
|
976
981
|
});
|
|
@@ -1247,7 +1252,10 @@ async function vpsCmd(words) {
|
|
|
1247
1252
|
const ssh = require('../lib/ssh');
|
|
1248
1253
|
const sub = (words[0] || '').toLowerCase();
|
|
1249
1254
|
const rest = words.slice(1);
|
|
1250
|
-
|
|
1255
|
+
// Lê a flag do process.argv CRU: o parser de topo tira os nomes de flag (--host…) dos
|
|
1256
|
+
// positionais (POS), então `words` só traz os VALORES soltos e o pareamento se perdia
|
|
1257
|
+
// (ts vps set --host X --user Y ignorava tudo e salvava o perfil velho). O argv preserva par.
|
|
1258
|
+
const flag = (n) => { const i = process.argv.indexOf(n); return i >= 0 ? process.argv[i + 1] : null; };
|
|
1251
1259
|
|
|
1252
1260
|
// ts vps set --host IP --user ubuntu --chave caminho.pem (ou --login arquivo.txt / --senha ...)
|
|
1253
1261
|
if (sub === 'set' || sub === 'config' || sub === 'add') {
|
package/lib/ssh.js
CHANGED
|
@@ -88,10 +88,22 @@ function connect(opts = {}) {
|
|
|
88
88
|
function exec(cmd, { timeoutMs = 120000 } = {}) {
|
|
89
89
|
return new Promise((resolve, reject) => {
|
|
90
90
|
if (!_conn) return reject(new Error('Não conectado. Rode "ts vps" ou conectar_vps primeiro.'));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
let stdout = '', stderr = '', done = false, stream = null;
|
|
92
|
+
// Timeout GLOBAL da chamada — armado ANTES do exec, então dispara MESMO se _conn.exec
|
|
93
|
+
// nunca chamar o callback. Numa conexão meia-morta (TCP vivo, sshd travado) o exec ficava
|
|
94
|
+
// pendurado pra SEMPRE (bug do hang de 40min). Ao estourar, derruba a conexão pra a PRÓXIMA
|
|
95
|
+
// chamada reconectar do zero (via _lastGood) em vez de pendurar de novo.
|
|
96
|
+
const to = setTimeout(() => {
|
|
97
|
+
if (done) return; done = true;
|
|
98
|
+
try { if (stream) stream.close(); } catch (_) {}
|
|
99
|
+
try { if (_conn) _conn.end(); } catch (_) {}
|
|
100
|
+
_conn = null; _info = null;
|
|
101
|
+
resolve({ code: 124, stdout, stderr: (stderr || '') + '\n[timeout: sem resposta em ' + Math.round(timeoutMs / 1000) + 's — a conexão pode ter travado; reconecte e tente de novo, e rode tarefas longas em background com setsid]' });
|
|
102
|
+
}, timeoutMs);
|
|
103
|
+
_conn.exec(cmd, (err, s) => {
|
|
104
|
+
if (done) { try { s && s.close(); } catch (_) {} return; }
|
|
105
|
+
if (err) { done = true; clearTimeout(to); return reject(err); }
|
|
106
|
+
stream = s;
|
|
95
107
|
stream.on('close', (code) => { if (done) return; done = true; clearTimeout(to); resolve({ code: code == null ? 0 : code, stdout, stderr }); });
|
|
96
108
|
stream.on('data', (d) => { stdout += d.toString(); if (stdout.length > 200000) stdout = stdout.slice(-120000); });
|
|
97
109
|
stream.stderr.on('data', (d) => { stderr += d.toString(); if (stderr.length > 60000) stderr = stderr.slice(-40000); });
|
package/lib/tools.js
CHANGED
|
@@ -381,7 +381,14 @@ async function execute(name, input, opts = {}) {
|
|
|
381
381
|
}
|
|
382
382
|
case 'executar_remoto': {
|
|
383
383
|
const ssh = require('./ssh');
|
|
384
|
-
|
|
384
|
+
// Conexão caiu (ou um exec anterior estourou o timeout e a derrubou)? Reconecta 1x
|
|
385
|
+
// SOZINHO com a última que funcionou (_lastGood: mesmo host/user/chave) — sem pedir
|
|
386
|
+
// nada ao usuário. Assim um SSH que travou não pendura a missão: o timeout do exec
|
|
387
|
+
// devolve o controle, e a próxima chamada se reconecta e segue.
|
|
388
|
+
if (!ssh.isConnected()) {
|
|
389
|
+
if (ssh.lastGood()) { try { await ssh.connect(); } catch (_) {} }
|
|
390
|
+
if (!ssh.isConnected()) return { erro: 'Não conectado a nenhum servidor. Chame conectar_vps primeiro.' };
|
|
391
|
+
}
|
|
385
392
|
const comando = String(input.comando || '');
|
|
386
393
|
const out = await ssh.exec(comando, { timeoutMs: 180000 });
|
|
387
394
|
const c = compactor.compact(comando, out.stdout || '', { codigo: out.code });
|