terminal-smart-cli 0.97.29 → 0.97.30

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 CHANGED
@@ -1085,7 +1085,9 @@ async function runFlow(goal, { askFn = ui.ask, json = false, yes = false } = {})
1085
1085
  const token = needToken();
1086
1086
  const sp0 = ui.spinner(runLbl('planning')).start();
1087
1087
  let r;
1088
- try { r = await api('/api/ia/orchestrate', { method: 'POST', token, body: { goal }, timeoutMs: 120000 }); }
1088
+ // `ts run` é a execução autônoma do CLI: começa no modo Confiável para ter
1089
+ // planejamento e uma recuperação objetiva. O chat simples continua Rápido.
1090
+ try { r = await api('/api/ia/orchestrate', { method: 'POST', token, body: { goal, mode: 'reliable' }, timeoutMs: 120000 }); }
1089
1091
  catch (e) { sp0.stop(); throw e; }
1090
1092
  sp0.stop();
1091
1093
  let run = r.run;
@@ -1433,7 +1435,7 @@ async function agentCmd(words, { askFn, cwd: cwdIn = null, onCwd = null, maxIter
1433
1435
  // HEADLESS stream-json: fecha com assistant (texto) + result (métricas) e sai.
1434
1436
  if (streamJson) {
1435
1437
  _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, toolErrors: out.toolErrors || [], guard: out.guard || null, missionCache: out.missionCache || 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 }));
1438
+ _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
1439
  return;
1438
1440
  }
1439
1441
  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 +1533,7 @@ async function acpCmd() {
1531
1533
  // token pode faltar — o servidor responde o handshake mesmo assim e só recusa no prompt
1532
1534
  // (mensagem "rode ts login"). NÃO escreve NADA em stdout aqui (corromperia o JSON-RPC).
1533
1535
  const { acpServer } = require('../lib/acp');
1534
- acpServer({ token: cfg.token || null, lang: cfg.lang || 'pt' });
1536
+ acpServer({ token: cfg.token || null, lang: cfg.lang || 'pt', plan: process.env.TS_ACCOUNT_PLAN || cfg.plan || 'free' });
1535
1537
  // fica vivo lendo stdin até o editor fechar (rl 'close' → process.exit).
1536
1538
  return new Promise(() => {});
1537
1539
  }
@@ -1546,6 +1548,16 @@ async function evalCmd(words) {
1546
1548
  const _val = (names) => { const i = process.argv.findIndex(a => names.includes(a)); return i >= 0 ? (process.argv[i + 1] || null) : null; };
1547
1549
  const model = _val(['--modelo', '--model']); // executor do agente (bake-off)
1548
1550
  const judgeModel = _val(['--juiz', '--judge']) || undefined; // modelo do juiz (default barato)
1551
+ // Evals precisam de orçamento próprio: o laboratório não pode ficar preso
1552
+ // no teto padrão de uma missão só porque um provedor demorou a encerrar.
1553
+ const _evalSteps = Number(_val(['--passos', '--steps']));
1554
+ const _evalSeconds = Number(_val(['--tempo-max-seg', '--max-seconds']));
1555
+ const _evalCredits = Number(_val(['--orcamento-creditos', '--max-creditos']));
1556
+ const evalLimits = {
1557
+ maxIter: Number.isFinite(_evalSteps) && _evalSteps > 0 ? Math.min(60, Math.floor(_evalSteps)) : undefined,
1558
+ maxDurationMs: Number.isFinite(_evalSeconds) && _evalSeconds > 0 ? Math.min(3600, _evalSeconds) * 1000 : undefined,
1559
+ maxCredits: Number.isFinite(_evalCredits) && _evalCredits > 0 ? Math.floor(_evalCredits) : undefined,
1560
+ };
1549
1561
  const smoke = FLAGS.has('--smoke');
1550
1562
  // caminho da suíte = positional que não seja valor de --modelo/--juiz. Prefere um que
1551
1563
  // termine em .json ou exista em disco (robusto quando o valor de --modelo bate com o nome).
@@ -1585,7 +1597,7 @@ async function evalCmd(words) {
1585
1597
  let mx;
1586
1598
  try {
1587
1599
  mx = await evalMod.runMatrix(suite, {
1588
- token, lang: cfg.lang || 'pt', judgeModel, models,
1600
+ token, lang: cfg.lang || 'pt', judgeModel, models, ...evalLimits,
1589
1601
  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
1602
  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
1603
  });
@@ -1611,7 +1623,7 @@ async function evalCmd(words) {
1611
1623
  let res;
1612
1624
  try {
1613
1625
  res = await evalMod.runSuite(suite, {
1614
- token, lang: cfg.lang || 'pt', model, judgeModel,
1626
+ token, lang: cfg.lang || 'pt', model, judgeModel, ...evalLimits,
1615
1627
  onCase: ({ i, total, id, phase, pass, score }) => {
1616
1628
  if (JSON_OUT) return;
1617
1629
  if (phase === 'done') {
@@ -1733,6 +1745,47 @@ async function vpsCmd(words) {
1733
1745
  } catch (e) { sp && sp.stop && sp.stop(); console.log('\n ' + C.err('✗') + ' ' + e.message + '\n'); }
1734
1746
  }
1735
1747
 
1748
+ // ── ts gateway: aliases OpenSSH com operação forçada ────────────────────────
1749
+ function gatewayCmd(words) {
1750
+ const gateways = require('../lib/gateways');
1751
+ const sub = String(words[0] || 'listar').toLowerCase();
1752
+ const flag = (names) => { const i = rawArgs.findIndex(a => names.includes(a)); return i >= 0 ? rawArgs[i + 1] : null; };
1753
+ // PowerShell separa valores com vírgula em múltiplos argumentos quando eles
1754
+ // não estão entre aspas. Aceitamos as duas formas: status,logs e "status,logs".
1755
+ const flagList = (names) => {
1756
+ const i = rawArgs.findIndex(a => names.includes(a));
1757
+ if (i < 0) return null;
1758
+ const values = [];
1759
+ for (let p = i + 1; p < rawArgs.length && !String(rawArgs[p]).startsWith('-'); p++) values.push(rawArgs[p]);
1760
+ return values.join(',');
1761
+ };
1762
+ if (['listar', 'list', 'ls', 'ver', 'status'].includes(sub)) {
1763
+ const rows = gateways.list();
1764
+ if (!rows.length) { console.log(ui.infoLine('Nenhum gateway configurado. Exemplo: ts gateway adicionar producao --alias meu-gateway --operacoes status,logs,deploy')); return; }
1765
+ console.log('');
1766
+ for (const item of rows) console.log(` ${C.cyan(item.name)} ${C.bold(item.label)}${C.dim(' · alias ' + item.sshAlias + ' · ' + item.operations.join(', '))}`);
1767
+ console.log('');
1768
+ return;
1769
+ }
1770
+ if (['adicionar', 'add', 'set', 'configurar', 'config'].includes(sub)) {
1771
+ const name = words[1];
1772
+ const alias = flag(['--alias', '--ssh-alias']);
1773
+ const operations = flagList(['--operacoes', '--operations']) || 'status';
1774
+ const label = flag(['--nome', '--label']) || name;
1775
+ try {
1776
+ const item = gateways.save(name, { label, sshAlias: alias, operations });
1777
+ console.log(ui.okLine(`Gateway salvo: ${item.label} · alias ${item.sshAlias} · ${item.operations.join(', ')}`));
1778
+ } catch (e) { console.log(ui.errLine(e.message)); }
1779
+ return;
1780
+ }
1781
+ if (['remover', 'remove', 'rm', 'apagar'].includes(sub)) {
1782
+ const removed = gateways.remove(words[1]);
1783
+ console.log(removed ? ui.okLine('Gateway removido.') : ui.infoLine('Gateway não encontrado.'));
1784
+ return;
1785
+ }
1786
+ console.log(ui.infoLine('Uso: ts gateway listar | adicionar <nome> --alias <alias-openssh> --operacoes status,logs,deploy | remover <nome>'));
1787
+ }
1788
+
1736
1789
  // ── ts meta: MISSÃO longa (checklist + rodadas até terminar — modo noturno) ──
1737
1790
  function _metaChecklistBox(itens) {
1738
1791
  return ui.box(itens.map(it =>
@@ -3195,6 +3248,7 @@ function recallCmd(args) {
3195
3248
  case 'memoria': case 'memória': case 'memory': return memoriaCmd(POS.slice(1));
3196
3249
  case 'recall': case 'lembrei': case 'sessoes': case 'sessões': return recallCmd(POS.slice(1));
3197
3250
  case 'vps': case 'servidor': return vpsCmd(POS.slice(1));
3251
+ case 'gateway': case 'gateways': return gatewayCmd(POS.slice(1));
3198
3252
  case 'site': case 'sites': case 'pagina': return siteCmd(POS.slice(1));
3199
3253
  case 'cloud': case 'nuvem': return cloudCmd(POS.slice(1));
3200
3254
  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: ({ name, detail, blocked }) => {
105
- const tcid = 'tc-' + (++seq); // id GLOBAL → nunca colide entre prompts/sessões
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 { stopReason: 'end_turn' };
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),