terminal-smart-cli 0.97.29 → 0.97.31
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 +73 -6
- package/lib/acp.js +40 -18
- package/lib/agent.js +522 -73
- package/lib/core.js +11 -4
- package/lib/eval.js +2 -2
- package/lib/evolution-proposal.js +47 -0
- package/lib/file-lock.js +42 -0
- package/lib/gateways.js +69 -0
- package/lib/intelligence-core.js +115 -33
- package/lib/meta.js +6 -2
- package/lib/owner-audit.js +112 -0
- package/lib/providers.js +4 -1
- package/lib/recovery.js +12 -0
- package/lib/tools.js +145 -25
- package/lib/ui.js +10 -0
- package/package.json +2 -2
package/bin/ts.js
CHANGED
|
@@ -1079,13 +1079,28 @@ async function pollRun(token, runId, sp) {
|
|
|
1079
1079
|
}
|
|
1080
1080
|
}
|
|
1081
1081
|
|
|
1082
|
+
// A run é o resumo; o motivo técnico da falha fica na etapa. Centralizar a
|
|
1083
|
+
// leitura evita que o usuário receba apenas "falhou" e tenha de investigar
|
|
1084
|
+
// logs ou a interface web para saber o que fazer em seguida.
|
|
1085
|
+
function runFailureMessage(run) {
|
|
1086
|
+
const failed = (run && Array.isArray(run.steps) ? run.steps : []).find(s => s && s.status === 'failed');
|
|
1087
|
+
const raw = String((failed && (failed.error_message || failed.result_summary)) || (run && run.error) || '').trim();
|
|
1088
|
+
const code = String((failed && failed.error_code) || '').trim();
|
|
1089
|
+
if (code === 'execution_unavailable' || /n[aã]o executo comandos diretamente|n[aã]o tenho acesso.*(windows|computador|m[aá]quina|sistema)|execute.*powershell/i.test(raw)) {
|
|
1090
|
+
return 'Esta orquestração roda na nuvem e não acessa arquivos do seu computador. Para criar, editar ou inspecionar algo local, use: ts agente "sua tarefa".';
|
|
1091
|
+
}
|
|
1092
|
+
return raw || 'Nenhuma ação foi concluída. Consulte as etapas da execução para ver o motivo.';
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1082
1095
|
// Fluxo completo de orquestração (usado pelo `ts run` e pelo /run do modo conversa).
|
|
1083
1096
|
// askFn permite aprovar com o readline do REPL sem conflito de stdin; nunca chama process.exit.
|
|
1084
1097
|
async function runFlow(goal, { askFn = ui.ask, json = false, yes = false } = {}) {
|
|
1085
1098
|
const token = needToken();
|
|
1086
1099
|
const sp0 = ui.spinner(runLbl('planning')).start();
|
|
1087
1100
|
let r;
|
|
1088
|
-
|
|
1101
|
+
// `ts run` é a execução autônoma do CLI: começa no modo Confiável para ter
|
|
1102
|
+
// planejamento e uma recuperação objetiva. O chat simples continua Rápido.
|
|
1103
|
+
try { r = await api('/api/ia/orchestrate', { method: 'POST', token, body: { goal, mode: 'reliable' }, timeoutMs: 120000 }); }
|
|
1089
1104
|
catch (e) { sp0.stop(); throw e; }
|
|
1090
1105
|
sp0.stop();
|
|
1091
1106
|
let run = r.run;
|
|
@@ -1110,7 +1125,7 @@ async function runFlow(goal, { askFn = ui.ask, json = false, yes = false } = {})
|
|
|
1110
1125
|
const result = run.final_result || run.result || '';
|
|
1111
1126
|
if (result) { console.log(''); console.log(ui.md(String(result)).split('\n').map(l => ' ' + l).join('\n')); console.log(''); }
|
|
1112
1127
|
} else if (run.status === 'failed') {
|
|
1113
|
-
console.error(ui.errLine(C.bold(T.run_failed) +
|
|
1128
|
+
console.error(ui.errLine(C.bold(T.run_failed) + C.dim(' · ' + runFailureMessage(run).slice(0, 320))));
|
|
1114
1129
|
} else {
|
|
1115
1130
|
console.log(ui.infoLine(T.run_paused(runLbl(run.status))));
|
|
1116
1131
|
}
|
|
@@ -1433,7 +1448,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
|
|
|
1433
1448
|
// HEADLESS stream-json: fecha com assistant (texto) + result (métricas) e sai.
|
|
1434
1449
|
if (streamJson) {
|
|
1435
1450
|
_emit(core.AgentEvents.assistant(out.text || ''));
|
|
1436
|
-
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null,
|
|
1451
|
+
_emit(core.AgentEvents.result({ steps: out.steps, credits: out.credits, tokens: out.tokens, context: out.context || null, needHuman: out.needHuman || null, guard: out.guard || null, observability: out.observability || null, duration_ms: Date.now() - t0, worktree: _wt ? { branch: _wt.branch, changed: (_wtInfo && _wtInfo.changed) || 0 } : null }));
|
|
1437
1452
|
return;
|
|
1438
1453
|
}
|
|
1439
1454
|
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, guard: out.guard || null })); return; }
|
|
@@ -1531,7 +1546,7 @@ async function acpCmd() {
|
|
|
1531
1546
|
// token pode faltar — o servidor responde o handshake mesmo assim e só recusa no prompt
|
|
1532
1547
|
// (mensagem "rode ts login"). NÃO escreve NADA em stdout aqui (corromperia o JSON-RPC).
|
|
1533
1548
|
const { acpServer } = require('../lib/acp');
|
|
1534
|
-
acpServer({ token: cfg.token || null, lang: cfg.lang || 'pt' });
|
|
1549
|
+
acpServer({ token: cfg.token || null, lang: cfg.lang || 'pt', plan: process.env.TS_ACCOUNT_PLAN || cfg.plan || 'free' });
|
|
1535
1550
|
// fica vivo lendo stdin até o editor fechar (rl 'close' → process.exit).
|
|
1536
1551
|
return new Promise(() => {});
|
|
1537
1552
|
}
|
|
@@ -1546,6 +1561,16 @@ async function evalCmd(words) {
|
|
|
1546
1561
|
const _val = (names) => { const i = process.argv.findIndex(a => names.includes(a)); return i >= 0 ? (process.argv[i + 1] || null) : null; };
|
|
1547
1562
|
const model = _val(['--modelo', '--model']); // executor do agente (bake-off)
|
|
1548
1563
|
const judgeModel = _val(['--juiz', '--judge']) || undefined; // modelo do juiz (default barato)
|
|
1564
|
+
// Evals precisam de orçamento próprio: o laboratório não pode ficar preso
|
|
1565
|
+
// no teto padrão de uma missão só porque um provedor demorou a encerrar.
|
|
1566
|
+
const _evalSteps = Number(_val(['--passos', '--steps']));
|
|
1567
|
+
const _evalSeconds = Number(_val(['--tempo-max-seg', '--max-seconds']));
|
|
1568
|
+
const _evalCredits = Number(_val(['--orcamento-creditos', '--max-creditos']));
|
|
1569
|
+
const evalLimits = {
|
|
1570
|
+
maxIter: Number.isFinite(_evalSteps) && _evalSteps > 0 ? Math.min(60, Math.floor(_evalSteps)) : undefined,
|
|
1571
|
+
maxDurationMs: Number.isFinite(_evalSeconds) && _evalSeconds > 0 ? Math.min(3600, _evalSeconds) * 1000 : undefined,
|
|
1572
|
+
maxCredits: Number.isFinite(_evalCredits) && _evalCredits > 0 ? Math.floor(_evalCredits) : undefined,
|
|
1573
|
+
};
|
|
1549
1574
|
const smoke = FLAGS.has('--smoke');
|
|
1550
1575
|
// caminho da suíte = positional que não seja valor de --modelo/--juiz. Prefere um que
|
|
1551
1576
|
// termine em .json ou exista em disco (robusto quando o valor de --modelo bate com o nome).
|
|
@@ -1585,7 +1610,7 @@ async function evalCmd(words) {
|
|
|
1585
1610
|
let mx;
|
|
1586
1611
|
try {
|
|
1587
1612
|
mx = await evalMod.runMatrix(suite, {
|
|
1588
|
-
token, lang: cfg.lang || 'pt', judgeModel, models,
|
|
1613
|
+
token, lang: cfg.lang || 'pt', judgeModel, models, ...evalLimits,
|
|
1589
1614
|
onModel: ({ model, phase, row }) => { if (JSON_OUT) return; if (phase === 'start') spM.text((en ? 'testing ' : 'testando ') + model + '…'); else if (phase === 'done') { spM.stop(); console.log(' ' + C.cyan('◆') + ' ' + C.bold(model) + C.dim(' ' + row.passed + '/' + row.total + ' · score ' + row.avgScore + ' · ' + row.credits + ' cr') + (row.error ? C.err(' ' + row.error) : '')); spM.start(); } },
|
|
1590
1615
|
onCase: ({ phase, id, pass, score }) => { if (!JSON_OUT && phase === 'done') { spM.stop(); console.log(' ' + (pass ? C.ok('✔') : C.err('✗')) + C.dim(' ' + id + ' ' + score)); spM.start(); } },
|
|
1591
1616
|
});
|
|
@@ -1611,7 +1636,7 @@ async function evalCmd(words) {
|
|
|
1611
1636
|
let res;
|
|
1612
1637
|
try {
|
|
1613
1638
|
res = await evalMod.runSuite(suite, {
|
|
1614
|
-
token, lang: cfg.lang || 'pt', model, judgeModel,
|
|
1639
|
+
token, lang: cfg.lang || 'pt', model, judgeModel, ...evalLimits,
|
|
1615
1640
|
onCase: ({ i, total, id, phase, pass, score }) => {
|
|
1616
1641
|
if (JSON_OUT) return;
|
|
1617
1642
|
if (phase === 'done') {
|
|
@@ -1733,6 +1758,47 @@ async function vpsCmd(words) {
|
|
|
1733
1758
|
} catch (e) { sp && sp.stop && sp.stop(); console.log('\n ' + C.err('✗') + ' ' + e.message + '\n'); }
|
|
1734
1759
|
}
|
|
1735
1760
|
|
|
1761
|
+
// ── ts gateway: aliases OpenSSH com operação forçada ────────────────────────
|
|
1762
|
+
function gatewayCmd(words) {
|
|
1763
|
+
const gateways = require('../lib/gateways');
|
|
1764
|
+
const sub = String(words[0] || 'listar').toLowerCase();
|
|
1765
|
+
const flag = (names) => { const i = rawArgs.findIndex(a => names.includes(a)); return i >= 0 ? rawArgs[i + 1] : null; };
|
|
1766
|
+
// PowerShell separa valores com vírgula em múltiplos argumentos quando eles
|
|
1767
|
+
// não estão entre aspas. Aceitamos as duas formas: status,logs e "status,logs".
|
|
1768
|
+
const flagList = (names) => {
|
|
1769
|
+
const i = rawArgs.findIndex(a => names.includes(a));
|
|
1770
|
+
if (i < 0) return null;
|
|
1771
|
+
const values = [];
|
|
1772
|
+
for (let p = i + 1; p < rawArgs.length && !String(rawArgs[p]).startsWith('-'); p++) values.push(rawArgs[p]);
|
|
1773
|
+
return values.join(',');
|
|
1774
|
+
};
|
|
1775
|
+
if (['listar', 'list', 'ls', 'ver', 'status'].includes(sub)) {
|
|
1776
|
+
const rows = gateways.list();
|
|
1777
|
+
if (!rows.length) { console.log(ui.infoLine('Nenhum gateway configurado. Exemplo: ts gateway adicionar producao --alias meu-gateway --operacoes status,logs,deploy')); return; }
|
|
1778
|
+
console.log('');
|
|
1779
|
+
for (const item of rows) console.log(` ${C.cyan(item.name)} ${C.bold(item.label)}${C.dim(' · alias ' + item.sshAlias + ' · ' + item.operations.join(', '))}`);
|
|
1780
|
+
console.log('');
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (['adicionar', 'add', 'set', 'configurar', 'config'].includes(sub)) {
|
|
1784
|
+
const name = words[1];
|
|
1785
|
+
const alias = flag(['--alias', '--ssh-alias']);
|
|
1786
|
+
const operations = flagList(['--operacoes', '--operations']) || 'status';
|
|
1787
|
+
const label = flag(['--nome', '--label']) || name;
|
|
1788
|
+
try {
|
|
1789
|
+
const item = gateways.save(name, { label, sshAlias: alias, operations });
|
|
1790
|
+
console.log(ui.okLine(`Gateway salvo: ${item.label} · alias ${item.sshAlias} · ${item.operations.join(', ')}`));
|
|
1791
|
+
} catch (e) { console.log(ui.errLine(e.message)); }
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (['remover', 'remove', 'rm', 'apagar'].includes(sub)) {
|
|
1795
|
+
const removed = gateways.remove(words[1]);
|
|
1796
|
+
console.log(removed ? ui.okLine('Gateway removido.') : ui.infoLine('Gateway não encontrado.'));
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
console.log(ui.infoLine('Uso: ts gateway listar | adicionar <nome> --alias <alias-openssh> --operacoes status,logs,deploy | remover <nome>'));
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1736
1802
|
// ── ts meta: MISSÃO longa (checklist + rodadas até terminar — modo noturno) ──
|
|
1737
1803
|
function _metaChecklistBox(itens) {
|
|
1738
1804
|
return ui.box(itens.map(it =>
|
|
@@ -3195,6 +3261,7 @@ function recallCmd(args) {
|
|
|
3195
3261
|
case 'memoria': case 'memória': case 'memory': return memoriaCmd(POS.slice(1));
|
|
3196
3262
|
case 'recall': case 'lembrei': case 'sessoes': case 'sessões': return recallCmd(POS.slice(1));
|
|
3197
3263
|
case 'vps': case 'servidor': return vpsCmd(POS.slice(1));
|
|
3264
|
+
case 'gateway': case 'gateways': return gatewayCmd(POS.slice(1));
|
|
3198
3265
|
case 'site': case 'sites': case 'pagina': return siteCmd(POS.slice(1));
|
|
3199
3266
|
case 'cloud': case 'nuvem': return cloudCmd(POS.slice(1));
|
|
3200
3267
|
case 'diagnosticar': case 'diagnose': case 'investigar': case 'debug': return diagnosticarCmd(POS.slice(1));
|
package/lib/acp.js
CHANGED
|
@@ -33,7 +33,7 @@ function _kindOf(name) {
|
|
|
33
33
|
return 'other';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function acpServer({ token, lang = 'pt' }) {
|
|
36
|
+
function acpServer({ token, lang = 'pt', plan = 'free' }) {
|
|
37
37
|
const write = (obj) => { try { process.stdout.write(JSON.stringify(obj) + '\n'); } catch (_) {} };
|
|
38
38
|
const log = (...a) => { try { process.stderr.write('[ts acp] ' + a.join(' ') + '\n'); } catch (_) {} };
|
|
39
39
|
const notify = (method, params) => write({ jsonrpc: '2.0', method, params });
|
|
@@ -96,25 +96,42 @@ function acpServer({ token, lang = 'pt' }) {
|
|
|
96
96
|
sess.cancelled = false;
|
|
97
97
|
sess.running = true;
|
|
98
98
|
let out = null;
|
|
99
|
+
const openSteps = new Map();
|
|
100
|
+
const beginStep = ({ name, detail, blocked, phase, model, retry }) => {
|
|
101
|
+
const tcid = 'tc-' + (++seq);
|
|
102
|
+
const queue = openSteps.get(name) || [];
|
|
103
|
+
queue.push(tcid); openSteps.set(name, queue);
|
|
104
|
+
notify('session/update', { sessionId: sid, update: {
|
|
105
|
+
sessionUpdate: 'tool_call', toolCallId: tcid,
|
|
106
|
+
title: name, rawName: name, detail: detail || '', phase: phase || '', model: model || '',
|
|
107
|
+
kind: _kindOf(name), status: blocked ? 'failed' : 'in_progress',
|
|
108
|
+
} });
|
|
109
|
+
if (blocked || retry || ['compactar_contexto', 'gateway'].includes(String(name))) {
|
|
110
|
+
queue.shift(); if (queue.length) openSteps.set(name, queue); else openSteps.delete(name);
|
|
111
|
+
notify('session/update', { sessionId: sid, update: {
|
|
112
|
+
sessionUpdate: 'tool_call_update', toolCallId: tcid,
|
|
113
|
+
status: blocked ? 'failed' : 'completed', evidence: String(detail || '').slice(0, 800),
|
|
114
|
+
} });
|
|
115
|
+
}
|
|
116
|
+
return tcid;
|
|
117
|
+
};
|
|
118
|
+
const finishStep = ({ name, ok, status, evidence }) => {
|
|
119
|
+
const queue = openSteps.get(name) || [];
|
|
120
|
+
const tcid = queue.shift() || beginStep({ name, detail: '' });
|
|
121
|
+
if (queue.length) openSteps.set(name, queue); else openSteps.delete(name);
|
|
122
|
+
notify('session/update', { sessionId: sid, update: {
|
|
123
|
+
sessionUpdate: 'tool_call_update', toolCallId: tcid,
|
|
124
|
+
status: ok === false || status === 'error' ? 'failed' : 'completed',
|
|
125
|
+
evidence: String(evidence || '').slice(0, 800),
|
|
126
|
+
} });
|
|
127
|
+
};
|
|
99
128
|
try {
|
|
100
129
|
out = await agent.run(text, {
|
|
101
|
-
token, lang, yes: false, cwd: sess.cwd, confineDir: null,
|
|
130
|
+
token, lang, yes: false, cwd: sess.cwd, confineDir: null, accountPlan: plan,
|
|
102
131
|
shouldStop: () => sess.cancelled, // cancelamento cooperativo: para no próximo passo
|
|
103
|
-
onThinking: () => {},
|
|
104
|
-
onStep:
|
|
105
|
-
|
|
106
|
-
// tool_call (início) + tool_call_update (fim) — nosso onStep dispara já
|
|
107
|
-
// com o passo concluído, então mandamos os dois em sequência.
|
|
108
|
-
notify('session/update', { sessionId: sid, update: {
|
|
109
|
-
sessionUpdate: 'tool_call', toolCallId: tcid,
|
|
110
|
-
title: name + (detail ? ' · ' + detail : ''), kind: _kindOf(name),
|
|
111
|
-
status: blocked ? 'failed' : 'in_progress',
|
|
112
|
-
} });
|
|
113
|
-
notify('session/update', { sessionId: sid, update: {
|
|
114
|
-
sessionUpdate: 'tool_call_update', toolCallId: tcid,
|
|
115
|
-
status: blocked ? 'failed' : 'completed',
|
|
116
|
-
} });
|
|
117
|
-
},
|
|
132
|
+
onThinking: (progress) => notify('session/update', { sessionId: sid, update: Object.assign({ sessionUpdate: 'agent_progress' }, progress || {}) }),
|
|
133
|
+
onStep: beginStep,
|
|
134
|
+
onStepDone: finishStep,
|
|
118
135
|
askApprove: async (cmd) => {
|
|
119
136
|
if (sess.cancelled) return false;
|
|
120
137
|
// pede a decisão ao EDITOR (o usuário aprova/recusa na UI do IDE)
|
|
@@ -145,7 +162,12 @@ function acpServer({ token, lang = 'pt' }) {
|
|
|
145
162
|
if (sess.cancelled) return { stopReason: 'cancelled' };
|
|
146
163
|
// texto final do agente → chunk de mensagem
|
|
147
164
|
if (out && out.text) notify('session/update', { sessionId: sid, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: out.text } } });
|
|
148
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
stopReason: 'end_turn',
|
|
167
|
+
usage: out ? { inTok: out.tokens && out.tokens.inTok || 0, outTok: out.tokens && out.tokens.outTok || 0, cacheTok: out.tokens && out.tokens.cachedTok || 0, credits: out.credits || 0, model: out.model || '' } : {},
|
|
168
|
+
report: out && out.report || null,
|
|
169
|
+
completion: out && out.completion || null,
|
|
170
|
+
};
|
|
149
171
|
},
|
|
150
172
|
// NOTIFICAÇÃO (sem id): marca cancelamento (o agente para no próximo passo via
|
|
151
173
|
// shouldStop) E destrava qualquer permissão em voo DESTA sessão (→ nega na hora),
|