terminal-smart-cli 0.98.0 → 0.98.1
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/lib/agent.js +4 -3
- package/lib/intelligence-core.js +102 -0
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -965,7 +965,7 @@ async function _subAgent({ task, k, model, cwd, lang, onStep }) {
|
|
|
965
965
|
let inp = {}; try { inp = JSON.parse((tc.function && tc.function.arguments) || '{}'); } catch (_) {}
|
|
966
966
|
if (onStep) onStep({ name: ' ↳ ' + nm, detail: argsShort(nm, inp) });
|
|
967
967
|
const res = READONLY.has(nm) ? await tools.execute(nm, inp, { baseDir: cwd, token: k }) : { erro: 'ferramenta não permitida no sub-agente (só leitura).' };
|
|
968
|
-
msgs.push({ role: 'tool', tool_call_id: tc.id, content:
|
|
968
|
+
msgs.push({ role: 'tool', tool_call_id: tc.id, content: intelligence.compactToolResult(res, TOOL_RESULT_CAP) });
|
|
969
969
|
}
|
|
970
970
|
}
|
|
971
971
|
// Esgotou os passos ainda chamando ferramentas? Fecha com UM resumo (sem ferramentas) —
|
|
@@ -2368,7 +2368,7 @@ async function run(task, opts = {}) {
|
|
|
2368
2368
|
const _cacheClass = core.classifyToolResult(result);
|
|
2369
2369
|
if (_cacheClass.ok && READONLY.has(name)) {
|
|
2370
2370
|
const _resultCap = name === 'ler_arquivos' ? 24000 : TOOL_RESULT_CAP;
|
|
2371
|
-
_missionCache.remember(name, input, { toolCallId: tc.id, content:
|
|
2371
|
+
_missionCache.remember(name, input, { toolCallId: tc.id, content: intelligence.compactToolResult(result, _resultCap) });
|
|
2372
2372
|
} else if (_cacheClass.ok && !READONLY.has(name)) {
|
|
2373
2373
|
_missionCache.invalidate();
|
|
2374
2374
|
}
|
|
@@ -2395,7 +2395,8 @@ async function run(task, opts = {}) {
|
|
|
2395
2395
|
return { text: finalText, steps, credits: charged + _visionCredits, tokens: acc, model: usedModel, actions, cwd, context: lastCtx, needHuman: { motivo: result.motivo, o_que_fazer: result.o_que_fazer }, orchestration: { plan: _planDecision, roles: _roleTrace, inspections: _inspectionTrace } };
|
|
2396
2396
|
}
|
|
2397
2397
|
const _resultCap = name === 'ler_arquivos' ? 24000 : TOOL_RESULT_CAP;
|
|
2398
|
-
|
|
2398
|
+
// Era .slice() no JSON: cortava o FIM (onde está o erro) e entregava JSON quebrado.
|
|
2399
|
+
const _rawToolContent = _cachedContent || intelligence.compactToolResult(result, _resultCap);
|
|
2399
2400
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: isUntrustedToolOutput(name)
|
|
2400
2401
|
? untrustedToolEnvelope(name, _rawToolContent, lang)
|
|
2401
2402
|
: _rawToolContent });
|
package/lib/intelligence-core.js
CHANGED
|
@@ -424,6 +424,107 @@ function cleanOperationalText(value, max) {
|
|
|
424
424
|
.replace(/\s+/g, ' ').trim().slice(0, max || 300);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
// ── Compactação de saída de ferramenta ──────────────────────────────────────
|
|
428
|
+
// Saída de ferramenta é a maior fatia do contexto de um agente — bem maior que o que o
|
|
429
|
+
// usuário escreve ou o que o modelo responde. E boa parte dela é ruído para o modelo:
|
|
430
|
+
// código de cor, barra de progresso, a mesma linha de log repetida mil vezes.
|
|
431
|
+
//
|
|
432
|
+
// O jeito ingênuo de conter isso é cortar com slice(). Três coisas dão errado:
|
|
433
|
+
// 1. corta o FIM, que é justamente onde fica o erro — a parte que importa;
|
|
434
|
+
// 2. em JSON, rasga a estrutura no meio de uma string e entrega sintaxe inválida;
|
|
435
|
+
// 3. mantém o ANSI e as repetições, que continuam ocupando espaço pago.
|
|
436
|
+
// Head/tail resolve as três: preserva o começo (o que foi pedido) e o fim (como acabou).
|
|
437
|
+
const TOOL_OUTPUT_MAX = 6000; // ~1.500 tokens por resultado
|
|
438
|
+
const _TOOL_FIELD_FLOOR = 240; // nenhum campo encolhe abaixo disto: sobra virava enigma
|
|
439
|
+
|
|
440
|
+
function _stripAnsi(value) {
|
|
441
|
+
return String(value == null ? '' : value)
|
|
442
|
+
.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
|
|
443
|
+
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, '');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Linhas consecutivas idênticas viram uma com contador. O modelo não aprende nada na
|
|
447
|
+
// milésima repetição que não soubesse na segunda — mas paga por todas.
|
|
448
|
+
function _dedupeLines(value) {
|
|
449
|
+
const lines = String(value).split('\n');
|
|
450
|
+
const out = [];
|
|
451
|
+
for (let i = 0; i < lines.length; i++) {
|
|
452
|
+
let j = i;
|
|
453
|
+
while (j + 1 < lines.length && lines[j + 1] === lines[i] && lines[i].trim()) j++;
|
|
454
|
+
const reps = j - i + 1;
|
|
455
|
+
out.push(reps > 2 ? lines[i] + ' [repetida ' + reps + 'x]' : lines[i]);
|
|
456
|
+
if (reps === 2) out.push(lines[i]);
|
|
457
|
+
i = j;
|
|
458
|
+
}
|
|
459
|
+
return out.join('\n');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 60% do orçamento para o começo, o resto para o fim. O começo diz o que a ferramenta
|
|
463
|
+
// estava fazendo; o fim diz como terminou. O miolo de uma saída grande é quase sempre
|
|
464
|
+
// a parte que se repete.
|
|
465
|
+
function _headTail(value, max) {
|
|
466
|
+
const s = String(value);
|
|
467
|
+
if (s.length <= max) return s;
|
|
468
|
+
const marca = (n) => '\n… [' + n + ' chars omitidos] …\n';
|
|
469
|
+
const head = Math.floor(max * 0.6);
|
|
470
|
+
const tail = Math.max(0, max - head - marca(s.length).length);
|
|
471
|
+
return s.slice(0, head) + marca(s.length - head - tail) + s.slice(s.length - tail);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function _limparStrings(node, depth, orcamento) {
|
|
475
|
+
if (depth > 6 || orcamento.n <= 0) return node;
|
|
476
|
+
if (typeof node === 'string') { orcamento.n--; return _dedupeLines(_stripAnsi(node)); }
|
|
477
|
+
if (Array.isArray(node)) return node.map(v => _limparStrings(v, depth + 1, orcamento));
|
|
478
|
+
if (node && typeof node === 'object') {
|
|
479
|
+
const out = {};
|
|
480
|
+
for (const k of Object.keys(node)) out[k] = _limparStrings(node[k], depth + 1, orcamento);
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
return node;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function _maiorCampo(node, depth, achado) {
|
|
487
|
+
if (depth > 6) return achado;
|
|
488
|
+
if (Array.isArray(node)) { node.forEach((v, i) => _maiorCampo(v, depth + 1, _alvo(node, i, v, achado))); return achado; }
|
|
489
|
+
if (node && typeof node === 'object') {
|
|
490
|
+
for (const k of Object.keys(node)) { _alvo(node, k, node[k], achado); _maiorCampo(node[k], depth + 1, achado); }
|
|
491
|
+
}
|
|
492
|
+
return achado;
|
|
493
|
+
}
|
|
494
|
+
function _alvo(dono, chave, valor, achado) {
|
|
495
|
+
if (typeof valor === 'string' && valor.length > achado.len && valor.length > _TOOL_FIELD_FLOOR) {
|
|
496
|
+
achado.dono = dono; achado.chave = chave; achado.len = valor.length;
|
|
497
|
+
}
|
|
498
|
+
return achado;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Recebe o que a ferramenta devolveu (texto ou objeto) e entrega o que vai para o
|
|
502
|
+
// contexto. Objeto sai como JSON VÁLIDO: encolhe os campos de texto grandes, um de cada
|
|
503
|
+
// vez, começando pelo maior — em vez de cortar o JSON inteiro e entregar sintaxe quebrada.
|
|
504
|
+
function compactToolResult(value, max) {
|
|
505
|
+
const limite = Math.max(400, Number(max) || TOOL_OUTPUT_MAX);
|
|
506
|
+
if (value == null) return '';
|
|
507
|
+
if (typeof value !== 'object') return _headTail(_dedupeLines(_stripAnsi(value)), limite);
|
|
508
|
+
|
|
509
|
+
let obj = _limparStrings(value, 0, { n: 5000 });
|
|
510
|
+
let json = JSON.stringify(obj);
|
|
511
|
+
if (json == null) return '';
|
|
512
|
+
// Encolhe o maior campo de texto por vez. Cada volta tem que reduzir de verdade,
|
|
513
|
+
// senão isto viraria laço infinito num objeto que não dá mais para espremer.
|
|
514
|
+
for (let volta = 0; volta < 40 && json.length > limite; volta++) {
|
|
515
|
+
const alvo = _maiorCampo(obj, 0, { dono: null, chave: null, len: 0 });
|
|
516
|
+
if (!alvo.dono) break;
|
|
517
|
+
const sobra = json.length - limite;
|
|
518
|
+
const novo = Math.max(_TOOL_FIELD_FLOOR, alvo.len - sobra - 40);
|
|
519
|
+
if (novo >= alvo.len) break;
|
|
520
|
+
alvo.dono[alvo.chave] = _headTail(alvo.dono[alvo.chave], novo);
|
|
521
|
+
json = JSON.stringify(obj);
|
|
522
|
+
}
|
|
523
|
+
// Último recurso: objeto grande por QUANTIDADE de campos pequenos, não por tamanho de
|
|
524
|
+
// um campo. Aí não há o que espremer e o corte é assumido — mas ainda com o fim preservado.
|
|
525
|
+
return json.length > limite ? _headTail(json, limite) : json;
|
|
526
|
+
}
|
|
527
|
+
|
|
427
528
|
function _safeToolName(value) {
|
|
428
529
|
const name = String(value || 'unknown').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80);
|
|
429
530
|
return name || 'unknown';
|
|
@@ -614,6 +715,7 @@ module.exports = {
|
|
|
614
715
|
RETIRED_MODELS, supportedModelId,
|
|
615
716
|
installCatalogOverlay, resetCatalogOverlay, activeCatalog,
|
|
616
717
|
normalizeModelId, modelInfo, legacyPriceMap, normalizeUsage, estimateCostUsd, catalogFreshness,
|
|
718
|
+
TOOL_OUTPUT_MAX, compactToolResult,
|
|
617
719
|
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff, cleanOperationalText, buildOperationalSummary,
|
|
618
720
|
normalizeAgentPlan, agentRoleContract, routeModel,
|
|
619
721
|
};
|