terminal-smart-cli 0.97.65 → 0.97.67
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 +16 -3
- package/lib/core.js +50 -6
- package/lib/eval.js +3 -1
- package/lib/evolution-telemetry.js +58 -2
- package/lib/intelligence-core.js +39 -12
- package/lib/mission-tool-cache.js +12 -0
- package/lib/router.js +12 -2
- package/package.json +2 -2
package/lib/agent.js
CHANGED
|
@@ -89,11 +89,18 @@ function missionGuardDecision({ credits = 0, maxCredits = DEFAULT_MISSION_CREDIT
|
|
|
89
89
|
// primeiro e os fallbacks permanecem dentro do contrato do plano.
|
|
90
90
|
function planUpgradeLimit(plan) {
|
|
91
91
|
const id = intelligence.normalizeAgentPlan ? intelligence.normalizeAgentPlan(plan) : String(plan || 'free').toLowerCase();
|
|
92
|
-
|
|
92
|
+
// O Free passou de 0 para 1 degrau. Não é upgrade comercial: a contingência
|
|
93
|
+
// do Free é Luna (0.10/0.60), MAIS BARATA que o principal DeepSeek Pro
|
|
94
|
+
// (0.63/1.26). Com 0 degraus a contingência anunciada no plano ("DeepSeek Pro
|
|
95
|
+
// no chat, com contingência Luna") era cortada pelo slice e nunca existia.
|
|
96
|
+
return id === 'pro' ? 2 : 1;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
function executorFallbackChain({ selectedModel, forced = false, source = 'cloud', plan = 'free', complex = false } = {}) {
|
|
96
|
-
|
|
100
|
+
// Um id aposentado pode chegar aqui de config antiga, de histórico ou do
|
|
101
|
+
// roteador de outra versão. Converter na entrada é o que impede a cadeia
|
|
102
|
+
// inteira de apontar para um modelo que o gateway não serve mais.
|
|
103
|
+
const first = intelligence.supportedModelId(String(selectedModel || DEFAULT_EXECUTOR).trim());
|
|
97
104
|
if (forced) return [first];
|
|
98
105
|
const role = intelligence.agentRoleContract('executor', plan, { complex });
|
|
99
106
|
// O roteador pode selecionar um executor mais específico que o contrato padrão.
|
|
@@ -2296,7 +2303,13 @@ async function run(task, opts = {}) {
|
|
|
2296
2303
|
if (!_tr.ok && _tr.status !== 'blocked') {
|
|
2297
2304
|
const _shellHint = name === 'executar_comando' ? commandRecoveryHint(input.comando, _tr.evidence) : '';
|
|
2298
2305
|
if (_shellHint && result && typeof result === 'object') result = Object.assign({}, result, { _recovery: _shellHint });
|
|
2299
|
-
|
|
2306
|
+
// O sinal estruturado vem do ToolResult tipado — exitCode, errno e hadStderr, que
|
|
2307
|
+
// o core ja calculava e descartava — mais o pathKind, derivado AQUI a partir dos
|
|
2308
|
+
// argumentos da ferramenta: so o resultado ('relative' | 'absolute') viaja, o
|
|
2309
|
+
// caminho em si nunca sai da maquina.
|
|
2310
|
+
_toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence,
|
|
2311
|
+
exitCode: _tr.exitCode, errno: _tr.errno, hadStderr: _tr.hadStderr,
|
|
2312
|
+
pathKind: require('./evolution-telemetry').pathKindOf(input) });
|
|
2300
2313
|
const _failureKey = failureFingerprint(name, _tr, result);
|
|
2301
2314
|
const _equivalentFailures = (_failureCounts.get(_failureKey) || 0) + 1;
|
|
2302
2315
|
_failureCounts.set(_failureKey, _equivalentFailures);
|
package/lib/core.js
CHANGED
|
@@ -95,7 +95,10 @@ const MODEL_WINDOWS = {
|
|
|
95
95
|
'minimax': 200000, 'qwen': 64000, 'gemini': 500000, 'mimo': 128000,
|
|
96
96
|
};
|
|
97
97
|
const CTX_WINDOW_DEFAULT = 100000; // modelo desconhecido → conservador
|
|
98
|
-
|
|
98
|
+
// deepseek-v4-flash venceu o bake-off de código em 2026-08 e era o executor
|
|
99
|
+
// padrão, mas foi aposentado do gateway. O padrão passa a ser o executor do
|
|
100
|
+
// contrato oficial (AGENT_ROLES.executor), que é o mesmo nas três superfícies.
|
|
101
|
+
const DEFAULT_EXECUTOR = 'deepseek-v4-pro';
|
|
99
102
|
// limiar/rabo têm override por env (TS_COMPACT_AT / TS_COMPACT_TAIL) — usado nos testes.
|
|
100
103
|
const COMPACT_AT = Number(process.env.TS_COMPACT_AT) > 0 ? Number(process.env.TS_COMPACT_AT) : 0.65;
|
|
101
104
|
// piso de 6: rabo curto demais apaga a memória de trabalho e o agente relê arquivos em loop
|
|
@@ -167,11 +170,44 @@ function classifyError(msg) {
|
|
|
167
170
|
return 'unknown';
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
// raw = retorno de uma ferramenta (execute). Devolve
|
|
173
|
+
// raw = retorno de uma ferramenta (execute). Devolve
|
|
174
|
+
// { ok, status, errorClass, retryable, evidence, exitCode, errno, hadStderr }.
|
|
171
175
|
// NÃO muta o raw. Convenções do TS: {erro} = falha; {codigo|exitCode}≠0 = falha de shell;
|
|
172
176
|
// {written|created|resumo|stdout|...} = evidência de sucesso.
|
|
177
|
+
//
|
|
178
|
+
// Os três últimos campos entraram em 12/09/2026 e são o SINAL ESTRUTURADO que a telemetria
|
|
179
|
+
// do harness passou a aceitar. Eles já estavam aqui dentro: `exit` e `errMsg` eram
|
|
180
|
+
// calculados e descartados. Sem eles, três falhas com correções completamente diferentes
|
|
181
|
+
// chegavam ao banco como a mesma linha:
|
|
182
|
+
//
|
|
183
|
+
// ENOENT o arquivo não existe -> criar, ou corrigir o caminho
|
|
184
|
+
// EACCES existe, mas sem permissão -> permissão, ou outro caminho
|
|
185
|
+
// EISDIR pediram para ler um diretório -> usar listar_diretorio
|
|
186
|
+
//
|
|
187
|
+
// As três eram `not_found`. O mesmo vale para o código de saída: 127 é comando que não
|
|
188
|
+
// existe, 126 é arquivo sem permissão de execução, 1 é o programa rodou e reprovou.
|
|
189
|
+
//
|
|
190
|
+
// Nada aqui carrega conteúdo de usuário: errno é vocabulário do sistema operacional e sai
|
|
191
|
+
// de uma lista fechada, exitCode é um inteiro e hadStderr é booleano.
|
|
192
|
+
const ERRNOS_CONHECIDOS = new Set(['ENOENT', 'EACCES', 'EPERM', 'EISDIR', 'ENOTDIR', 'EEXIST',
|
|
193
|
+
'ENOTEMPTY', 'ENOSPC', 'EROFS', 'EMFILE', 'ENFILE', 'ENAMETOOLONG', 'EBUSY', 'EIO', 'EINVAL',
|
|
194
|
+
'EAGAIN', 'ETIMEDOUT', 'ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH', 'ENETUNREACH',
|
|
195
|
+
'EADDRINUSE', 'ENOTFOUND', 'EAI_AGAIN']);
|
|
196
|
+
|
|
197
|
+
// Lista fechada, e não um regex solto: `\bE[A-Z]+\b` casaria com a palavra "ERROR", que
|
|
198
|
+
// aparece em quase toda mensagem de falha, e o campo viraria ruído em vez de sinal.
|
|
199
|
+
function extractErrno(msg) {
|
|
200
|
+
const candidatos = String(msg || '').toUpperCase().match(/\bE[A-Z_]{2,11}\b/g);
|
|
201
|
+
if (!candidatos) return '';
|
|
202
|
+
for (const c of candidatos) if (ERRNOS_CONHECIDOS.has(c)) return c;
|
|
203
|
+
return '';
|
|
204
|
+
}
|
|
205
|
+
|
|
173
206
|
function classifyToolResult(raw) {
|
|
174
|
-
if (raw == null)
|
|
207
|
+
if (raw == null) {
|
|
208
|
+
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: '',
|
|
209
|
+
exitCode: -1, errno: '', hadStderr: false };
|
|
210
|
+
}
|
|
175
211
|
// Alguns adaptadores legados retornam falhas como texto. Tratar todo texto como sucesso
|
|
176
212
|
// permitia que "ERRO ao executar..." virasse etapa concluída. Mantém strings normais como
|
|
177
213
|
// sucesso, mas reconhece prefixos inequívocos de falha/bloqueio.
|
|
@@ -181,13 +217,17 @@ function classifyToolResult(raw) {
|
|
|
181
217
|
if (failedText) {
|
|
182
218
|
const errorClass = classifyError(text);
|
|
183
219
|
return { ok: false, status: errorClass === 'blocked' ? 'blocked' : 'failed', errorClass,
|
|
184
|
-
retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300)
|
|
220
|
+
retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300),
|
|
221
|
+
exitCode: -1, errno: extractErrno(text), hadStderr: false };
|
|
185
222
|
}
|
|
186
|
-
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200)
|
|
223
|
+
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200),
|
|
224
|
+
exitCode: -1, errno: '', hadStderr: false };
|
|
187
225
|
}
|
|
188
226
|
const exit = raw.codigo != null ? raw.codigo : (raw.exitCode != null ? raw.exitCode : null);
|
|
189
227
|
const errMsg = raw.erro || raw.error || (exit != null && exit !== 0 ? (raw.stderr || raw.stdout || ('exit ' + exit)) : '');
|
|
190
228
|
const failed = !!raw.erro || !!raw.error || (exit != null && exit !== 0);
|
|
229
|
+
const exitCode = exit != null && Number.isFinite(Number(exit)) ? Number(exit) : -1;
|
|
230
|
+
const hadStderr = !!(raw.stderr && String(raw.stderr).trim());
|
|
191
231
|
if (failed) {
|
|
192
232
|
const errorClass = classifyError(errMsg);
|
|
193
233
|
return {
|
|
@@ -196,10 +236,14 @@ function classifyToolResult(raw) {
|
|
|
196
236
|
errorClass,
|
|
197
237
|
retryable: RETRYABLE_CLASSES.has(errorClass),
|
|
198
238
|
evidence: String(errMsg).replace(/\s+/g, ' ').slice(0, 300),
|
|
239
|
+
exitCode,
|
|
240
|
+
errno: extractErrno(errMsg),
|
|
241
|
+
hadStderr,
|
|
199
242
|
};
|
|
200
243
|
}
|
|
201
244
|
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)') : '');
|
|
202
|
-
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: String(ev || '').replace(/\s+/g, ' ').slice(0, 200)
|
|
245
|
+
return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: String(ev || '').replace(/\s+/g, ' ').slice(0, 200),
|
|
246
|
+
exitCode, errno: '', hadStderr };
|
|
203
247
|
}
|
|
204
248
|
|
|
205
249
|
// ── SEGREDOS: nunca imprimir chave/token na saída (B18) ───────────────────────
|
package/lib/eval.js
CHANGED
|
@@ -24,7 +24,9 @@ const agent = require('./agent');
|
|
|
24
24
|
const intelligence = require('./intelligence-core');
|
|
25
25
|
|
|
26
26
|
// modelo default do JUIZ: barato e suficiente pra avaliar critério objetivo.
|
|
27
|
-
|
|
27
|
+
// Era deepseek-v4-flash, aposentado. Luna custa 0.10/0.60, tem `review` no
|
|
28
|
+
// catálogo e é mais barata que o Flash aposentado era.
|
|
29
|
+
const DEFAULT_JUDGE = 'gpt-5.6-luna';
|
|
28
30
|
|
|
29
31
|
// ── suíte ────────────────────────────────────────────────────────────────────
|
|
30
32
|
function loadSuite(p) {
|
|
@@ -18,6 +18,54 @@ function sanitizePattern(value) {
|
|
|
18
18
|
.replace(/\s+/g, ' ').trim().slice(0, 300);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// ── Sinal estruturado, de vocabulário FECHADO ────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// Estes quatro campos entraram em 12/09/2026, junto com as colunas correspondentes no
|
|
24
|
+
// servidor. Antes, o incidente carregava três categorias (ferramenta, classe, estágio) e
|
|
25
|
+
// um `pattern` derivado da própria classe — que, por derivar dela, não acrescentava
|
|
26
|
+
// entropia nenhuma ao fingerprint. ENOENT e EACCES na mesma ferramenta viravam a MESMA
|
|
27
|
+
// linha no banco, e o revisor automático respondia, com razão, que os dados agregados não
|
|
28
|
+
// permitiam determinar a causa.
|
|
29
|
+
//
|
|
30
|
+
// O vocabulário é fechado de propósito, e validado AQUI e de novo no servidor: são os dois
|
|
31
|
+
// únicos lugares em que um valor inesperado poderia virar texto livre, e texto livre nesta
|
|
32
|
+
// tabela é conteúdo de usuário vazando.
|
|
33
|
+
const ERRNOS_CONHECIDOS = new Set(['ENOENT', 'EACCES', 'EPERM', 'EISDIR', 'ENOTDIR', 'EEXIST',
|
|
34
|
+
'ENOTEMPTY', 'ENOSPC', 'EROFS', 'EMFILE', 'ENFILE', 'ENAMETOOLONG', 'EBUSY', 'EIO', 'EINVAL',
|
|
35
|
+
'EAGAIN', 'ETIMEDOUT', 'ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH', 'ENETUNREACH',
|
|
36
|
+
'EADDRINUSE', 'ENOTFOUND', 'EAI_AGAIN']);
|
|
37
|
+
|
|
38
|
+
function safeErrno(value) {
|
|
39
|
+
const code = String(value || '').trim().toUpperCase().replace(/[^A-Z_0-9]/g, '').slice(0, 20);
|
|
40
|
+
return ERRNOS_CONHECIDOS.has(code) ? code : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function safeExitCode(value) {
|
|
44
|
+
if (value === null || value === undefined || value === '') return -1;
|
|
45
|
+
const n = Number(value);
|
|
46
|
+
if (!Number.isFinite(n)) return -1;
|
|
47
|
+
return Math.max(-1, Math.min(255, Math.trunc(n)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Relativo ou absoluto responde a pergunta que o revisor fez em duas propostas diferentes:
|
|
51
|
+
// o harness errou o CAMINHO, ou errou o DIRETÓRIO DE TRABALHO? São correções distintas, e
|
|
52
|
+
// hoje as duas chegam como `not_found`.
|
|
53
|
+
//
|
|
54
|
+
// A derivação acontece aqui, no CLI, onde os argumentos da ferramenta existem — e só o
|
|
55
|
+
// resultado ('relative' | 'absolute') viaja. O caminho em si nunca sai da máquina.
|
|
56
|
+
function pathKindOf(args) {
|
|
57
|
+
if (!args || typeof args !== 'object') return '';
|
|
58
|
+
const alvo = args.caminho || args.arquivo || args.diretorio || args.origem || '';
|
|
59
|
+
const s = String(alvo).trim();
|
|
60
|
+
if (!s) return '';
|
|
61
|
+
return /^(?:[a-zA-Z]:[\\/]|\\\\|\/)/.test(s) ? 'absolute' : 'relative';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function safePathKind(value) {
|
|
65
|
+
const kind = String(value || '').trim().toLowerCase();
|
|
66
|
+
return kind === 'relative' || kind === 'absolute' ? kind : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
function buildEvents(toolErrors = [], actions = [], context = {}) {
|
|
22
70
|
const recoveredTools = new Set((actions || []).map(action => action && action.name).filter(Boolean));
|
|
23
71
|
const seen = new Set(); const events = [];
|
|
@@ -26,10 +74,18 @@ function buildEvents(toolErrors = [], actions = [], context = {}) {
|
|
|
26
74
|
const errorClass = String(error.errorClass || error.class || 'unknown').slice(0, 80);
|
|
27
75
|
const pattern = sanitizePattern(error.evidence || error.message || '');
|
|
28
76
|
const stage = String(error.stage || 'execution').slice(0, 40);
|
|
29
|
-
const
|
|
77
|
+
const errno = safeErrno(error.errno);
|
|
78
|
+
const exitCode = safeExitCode(error.exitCode);
|
|
79
|
+
const pathKind = safePathKind(error.pathKind);
|
|
80
|
+
const hadStderr = error.hadStderr === true;
|
|
81
|
+
// O fingerprint inclui o sinal novo: sem isso, duas causas diferentes continuariam
|
|
82
|
+
// deduplicadas aqui antes mesmo de chegar ao servidor.
|
|
83
|
+
const fingerprint = crypto.createHash('sha256')
|
|
84
|
+
.update([tool, errorClass, stage, pattern, errno, String(exitCode), pathKind].join('|')).digest('hex');
|
|
30
85
|
if (seen.has(fingerprint)) continue;
|
|
31
86
|
seen.add(fingerprint);
|
|
32
87
|
events.push({ tool, errorClass, pattern, stage, fingerprint,
|
|
88
|
+
errno, exitCode, pathKind, hadStderr,
|
|
33
89
|
retryable: error.retryable === true, recovered: recoveredTools.has(tool),
|
|
34
90
|
surface: context.surface || 'cli', appVersion: context.appVersion || 'unknown', model: context.model || 'unknown' });
|
|
35
91
|
if (events.length >= 20) break;
|
|
@@ -42,4 +98,4 @@ function enabled(config = {}, env = process.env) {
|
|
|
42
98
|
return config.diagnosticsEnabled !== false;
|
|
43
99
|
}
|
|
44
100
|
|
|
45
|
-
module.exports = { sanitizePattern, buildEvents, enabled };
|
|
101
|
+
module.exports = { sanitizePattern, buildEvents, enabled, pathKindOf };
|
package/lib/intelligence-core.js
CHANGED
|
@@ -171,14 +171,27 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
const MODEL_ALIASES = Object.freeze({
|
|
174
|
-
deepseek: 'deepseek-v4-
|
|
175
|
-
'deepseek-v4': 'deepseek-v4-
|
|
174
|
+
deepseek: 'deepseek-v4-pro',
|
|
175
|
+
'deepseek-v4': 'deepseek-v4-pro',
|
|
176
176
|
glm: 'glm-4.6',
|
|
177
177
|
mimo: 'mimo-v2.5',
|
|
178
|
-
'gemini-lite': 'gemini-2.5-flash
|
|
178
|
+
'gemini-lite': 'gemini-2.5-flash',
|
|
179
179
|
'claude-haiku': 'claude-haiku-4-5',
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
+
// Modelos aposentados do gateway do produto. Eles PERMANECEM no catalogo porque
|
|
183
|
+
// historico de consumo antigo ainda precisa ser precificado com o preco que
|
|
184
|
+
// valia na epoca; o que eles nao podem mais e entrar em contrato ativo ou em
|
|
185
|
+
// roteamento. A conversao mora AQUI, em cada superficie, e nao na reescrita que
|
|
186
|
+
// o backend faz no gate do proxy: uma superficie que dependesse daquela correcao
|
|
187
|
+
// ficaria errada em offline, em BYOK e em qualquer caminho que nao passe pelo
|
|
188
|
+
// proxy — foi exatamente o caso do CLI, cuja cadeia de executor do plano Free
|
|
189
|
+
// apontava inteira para os dois ids abaixo.
|
|
190
|
+
const RETIRED_MODELS = Object.freeze({
|
|
191
|
+
'deepseek-v4-flash': 'deepseek-v4-pro',
|
|
192
|
+
'gemini-2.5-flash-lite': 'gemini-2.5-flash',
|
|
193
|
+
});
|
|
194
|
+
|
|
182
195
|
// Equipe oficial do modo Automático: especialização e prova objetiva vêm antes
|
|
183
196
|
// de cascatas longas de modelos. Premium fica fora até validação real.
|
|
184
197
|
const OFFICIAL_AUTOMATIC_TEAM = Object.freeze({
|
|
@@ -194,23 +207,23 @@ const OFFICIAL_AUTOMATIC_TEAM = Object.freeze({
|
|
|
194
207
|
const AGENT_ROLES = Object.freeze({
|
|
195
208
|
classifier: Object.freeze({
|
|
196
209
|
prompt: 'Classifique a intenção e devolva somente a estrutura pedida. Não execute ferramentas nem invente dados.',
|
|
197
|
-
models: Object.freeze({ free: ['
|
|
210
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'gpt-4o-mini'], pro: ['deepseek-v4-pro', 'gpt-4o-mini'] }),
|
|
198
211
|
}),
|
|
199
212
|
planner: Object.freeze({
|
|
200
213
|
prompt: 'Decomponha o objetivo em etapas executáveis, dependências, riscos, critérios de aceite e provas. Não execute a tarefa.',
|
|
201
|
-
models: Object.freeze({ free: ['deepseek-v4-
|
|
214
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro'], pro: ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro'] }),
|
|
202
215
|
}),
|
|
203
216
|
executor: Object.freeze({
|
|
204
217
|
prompt: 'Execute somente a etapa recebida com as ferramentas autorizadas. Não declare sucesso sem resultado verificável.',
|
|
205
|
-
models: Object.freeze({ free: ['deepseek-v4-
|
|
218
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'] }),
|
|
206
219
|
}),
|
|
207
220
|
inspector: Object.freeze({
|
|
208
221
|
prompt: 'Inspecione em modo somente leitura e compare evidências com os critérios de aceite. Não altere nada e não elogie por cortesia.',
|
|
209
|
-
models: Object.freeze({ free: ['
|
|
222
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['gpt-5.6-luna', 'glm-5.2'], pro: ['gpt-5.6-luna', 'glm-5.2'] }),
|
|
210
223
|
}),
|
|
211
224
|
corrector: Object.freeze({
|
|
212
225
|
prompt: 'Corrija apenas falhas confirmadas pelo inspetor, preserve o que funciona e repita as provas afetadas. Não amplie o escopo.',
|
|
213
|
-
models: Object.freeze({ free: ['deepseek-v4-
|
|
226
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'] }),
|
|
214
227
|
}),
|
|
215
228
|
vision: Object.freeze({
|
|
216
229
|
prompt: 'Extraia fatos observáveis da imagem e separe leitura, inferência e incerteza. Não invente texto ilegível.',
|
|
@@ -222,11 +235,11 @@ const AGENT_ROLES = Object.freeze({
|
|
|
222
235
|
}),
|
|
223
236
|
site_builder: Object.freeze({
|
|
224
237
|
prompt: 'Crie ou edite o site completo, responsivo e acessível. Preserve o existente quando editar e valide a URL publicada.',
|
|
225
|
-
models: Object.freeze({ free: ['deepseek-v4-
|
|
238
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
226
239
|
}),
|
|
227
240
|
cloud_box: Object.freeze({
|
|
228
241
|
prompt: 'Opere a box com mudanças mínimas, registre comandos e valide serviço, porta e URL. Nunca exponha segredos.',
|
|
229
|
-
models: Object.freeze({ free: ['deepseek-v4-
|
|
242
|
+
models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
230
243
|
}),
|
|
231
244
|
});
|
|
232
245
|
|
|
@@ -256,6 +269,14 @@ function normalizeModelId(model) {
|
|
|
256
269
|
return upstream ? upstream[0] : transportFree;
|
|
257
270
|
}
|
|
258
271
|
|
|
272
|
+
// Camada de compatibilidade: recebe qualquer id (alias, id do upstream, id
|
|
273
|
+
// aposentado) e devolve um id que o produto ainda serve. Use isto — e nao
|
|
274
|
+
// normalizeModelId — em qualquer ponto que va CHAMAR um modelo.
|
|
275
|
+
function supportedModelId(model) {
|
|
276
|
+
const id = normalizeModelId(model);
|
|
277
|
+
return RETIRED_MODELS[id] || id;
|
|
278
|
+
}
|
|
279
|
+
|
|
259
280
|
function modelInfo(model, now) {
|
|
260
281
|
const id = normalizeModelId(model);
|
|
261
282
|
const info = MODEL_CATALOG[id] || {
|
|
@@ -512,7 +533,12 @@ function routeModel(task, reliability) {
|
|
|
512
533
|
if (t.kind === 'code') required.add('code');
|
|
513
534
|
if (t.kind === 'review') required.add('review');
|
|
514
535
|
if (t.kind === 'vision') required.add('vision');
|
|
515
|
-
|
|
536
|
+
// Aposentado nao volta por roteamento. Sem este filtro o CLI devolvia
|
|
537
|
+
// deepseek-v4-flash como melhor candidato de codigo e dependia do backend
|
|
538
|
+
// reescrever depois — o que nao acontece em BYOK nem offline.
|
|
539
|
+
const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(supportedModelId)
|
|
540
|
+
.filter(id => MODEL_CATALOG[id] && id !== 'smart')
|
|
541
|
+
.filter((id, i, list) => list.indexOf(id) === i);
|
|
516
542
|
const rel = reliability || {};
|
|
517
543
|
const plan = String(t.plan || '').toLowerCase();
|
|
518
544
|
const isPaid = plan && plan !== 'free' && plan !== 'gratuito';
|
|
@@ -527,12 +553,13 @@ function routeModel(task, reliability) {
|
|
|
527
553
|
const score = (observed * 0.35) + (m.quality * qualityWeight) + ((1 / (1 + price)) * costWeight) - (missing * 2);
|
|
528
554
|
return { id, score: +score.toFixed(6), missing };
|
|
529
555
|
}).sort((a, b) => b.score - a.score);
|
|
530
|
-
return { model: scored[0] ? scored[0].id :
|
|
556
|
+
return { model: scored[0] ? scored[0].id : 'deepseek-v4-pro', ranked: scored };
|
|
531
557
|
}
|
|
532
558
|
|
|
533
559
|
module.exports = {
|
|
534
560
|
INTELLIGENCE_CONTRACT, PRICE_REVISION, MODEL_CATALOG, MODEL_ALIASES, DEFAULT_PRICE,
|
|
535
561
|
MEMORY_TYPES, MEMORY_TRUST, CONTEXT_SOURCES, OFFICIAL_AUTOMATIC_TEAM, AGENT_ROLES,
|
|
562
|
+
RETIRED_MODELS, supportedModelId,
|
|
536
563
|
normalizeModelId, modelInfo, legacyPriceMap, normalizeUsage, estimateCostUsd, catalogFreshness,
|
|
537
564
|
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff, cleanOperationalText, buildOperationalSummary,
|
|
538
565
|
normalizeAgentPlan, agentRoleContract, routeModel,
|
|
@@ -44,6 +44,18 @@ class MissionToolCache {
|
|
|
44
44
|
stats() { return { entries: this.entries.size, hits: this.hits, invalidations: this.invalidations, charactersSaved: this.charactersSaved, estimatedTokensSaved: Math.ceil(this.charactersSaved / 4) }; }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// O terceiro parâmetro é IGNORADO de propósito, e continua na assinatura só para
|
|
48
|
+
// não quebrar os três pontos de chamada (cli/lib/agent.js, app-electron/main.js e
|
|
49
|
+
// backend/server/index.js), que ainda o calculam para a métrica de economia.
|
|
50
|
+
//
|
|
51
|
+
// Ele era `originalStillVisible`, e a decisão de encurtar dependia dele. O cálculo
|
|
52
|
+
// nas três superfícies verificava se existia uma mensagem com o mesmo
|
|
53
|
+
// `tool_call_id` — ou seja, se o ID estava visível, NÃO se o conteúdo ainda estava.
|
|
54
|
+
// Transports de assinatura pessoal reconstroem a conversa preservando o id e
|
|
55
|
+
// descartando o payload: o check aprovava, o modelo recebia "use o resultado
|
|
56
|
+
// anterior", não achava nada, relia o mesmo arquivo, recebia o mesmo ponteiro — e a
|
|
57
|
+
// missão só terminava quando o watchdog a matava. Não há como esta função saber se
|
|
58
|
+
// o payload sobreviveu, porque ela não vê a conversa; então ela não aposta.
|
|
47
59
|
function cacheReference(entry, tool, originalStillVisible) {
|
|
48
60
|
// Sempre devolva o conteúdo ao modelo. Alguns transports de assinatura pessoal
|
|
49
61
|
// reconstroem a conversa e preservam o id da tool call, mas não o payload antigo.
|
package/lib/router.js
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
const { api } = require('./api');
|
|
8
8
|
const keyring = require('./keyring');
|
|
9
9
|
|
|
10
|
+
// Modelo da camada 2 (classificacao de intencao). Era gemini-2.5-flash-lite,
|
|
11
|
+
// aposentado do gateway: a chamada falhava e o catch devolvia "chat" para tudo,
|
|
12
|
+
// o que fazia pedido de acao virar conversa. gemini-2.5-flash e o substituto
|
|
13
|
+
// da mesma familia, tem `classification` no catalogo, e a chamada usa
|
|
14
|
+
// max_completion_tokens: 5 — o custo por classificacao segue irrelevante.
|
|
15
|
+
const INTENT_MODEL = 'gemini-2.5-flash';
|
|
16
|
+
|
|
10
17
|
// Pergunta/conversa: forma interrogativa ou pedido de explicação
|
|
11
18
|
const Q_RE = /^(como|por ?qu[eê]|o ?que|qual|quais|quando|onde|quem|ser[aá] que|devo|posso|vale a pena|existe|tem como|é melhor|what|how|why|which|when|where|explique|me explica|explica|o que significa|diferen[çc]a entre)\b|\?\s*$/i;
|
|
12
19
|
|
|
@@ -67,7 +74,10 @@ async function classify(msg, token) {
|
|
|
67
74
|
method: 'POST', signal: ctrl.signal,
|
|
68
75
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + k.key },
|
|
69
76
|
body: JSON.stringify({
|
|
70
|
-
|
|
77
|
+
// Era gemini-2.5-flash-lite, aposentado: o gateway devolvia erro ou o
|
|
78
|
+
// backend reescrevia, e a classificação de intenção caía silenciosamente
|
|
79
|
+
// no catch que devolve "chat" para tudo.
|
|
80
|
+
model: keyring.modeloPara(k, INTENT_MODEL), stream: false, max_completion_tokens: 5,
|
|
71
81
|
messages: [
|
|
72
82
|
{ role: 'system', content: 'Classifique a intenção da mensagem de um usuário de um CLI DevOps. Responda APENAS uma palavra: "chat" (pergunta, conversa, pedido de explicação), "agente" (quer que algo seja FEITO nesta máquina: comandos, arquivos, diagnóstico, instalação) ou "orquestrar" (objetivo GRANDE com vários entregáveis para uma equipe de agentes).' },
|
|
73
83
|
{ role: 'user', content: String(msg).slice(0, 500) },
|
|
@@ -79,7 +89,7 @@ async function classify(msg, token) {
|
|
|
79
89
|
const word = String(j?.choices?.[0]?.message?.content || '').toLowerCase();
|
|
80
90
|
// cobra a classificação (mesmo padrão do web-intent) — best-effort
|
|
81
91
|
const u = j?.usage || {};
|
|
82
|
-
if (u.prompt_tokens && !k.billingAuthoritative) api('/api/credit/charge', { method: 'POST', token, body: { model:
|
|
92
|
+
if (u.prompt_tokens && !k.billingAuthoritative) api('/api/credit/charge', { method: 'POST', token, body: { model: INTENT_MODEL, inTok: u.prompt_tokens || 0, outTok: u.completion_tokens || 0 } }).catch(() => {});
|
|
83
93
|
if (word.includes('agente')) return 'agente';
|
|
84
94
|
if (word.includes('orquestrar')) return 'run';
|
|
85
95
|
return 'chat';
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-smart-cli",
|
|
3
|
-
"version": "0.97.
|
|
3
|
+
"version": "0.97.67",
|
|
4
4
|
"description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ts": "bin/ts.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "node
|
|
9
|
+
"test": "node scripts/run-tests.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"bin",
|