terminal-smart-cli 0.97.66 → 0.97.68
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 +21 -7
- package/lib/conversation-scope.js +7 -3
- package/lib/core.js +6 -3
- package/lib/eval.js +3 -1
- package/lib/intelligence-core.js +83 -44
- package/lib/mission-tool-cache.js +12 -0
- package/lib/router.js +12 -2
- package/lib/tools.js +2 -1
- package/package.json +2 -2
package/lib/agent.js
CHANGED
|
@@ -89,18 +89,26 @@ 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
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
106
|
+
// No modo Automático, a ordem do papel/plano é o contrato. O modelo sugerido
|
|
107
|
+
// pelo roteador pode ser um candidato válido e ainda assim representar um
|
|
108
|
+
// rebaixamento silencioso (por exemplo, V4.1 no lugar do Pro). Escolhas manuais
|
|
109
|
+
// já retornaram acima por `forced`; aqui usamos somente a cadeia contratada.
|
|
102
110
|
const candidates = role.candidates.filter(Boolean);
|
|
103
|
-
return [...new Set(
|
|
111
|
+
return [...new Set(candidates)]
|
|
104
112
|
.slice(0, 1 + planUpgradeLimit(plan));
|
|
105
113
|
}
|
|
106
114
|
|
|
@@ -479,7 +487,13 @@ function explicitTaskWorkdir(taskText, exists = fs.existsSync) {
|
|
|
479
487
|
// existente, preservando nomes de diretório que tenham espaços.
|
|
480
488
|
while (candidate) {
|
|
481
489
|
try {
|
|
482
|
-
|
|
490
|
+
// `exists` é injetável para que o parser de caminhos Windows possa ser
|
|
491
|
+
// testado também no Linux. A implementação padrão continua validando no
|
|
492
|
+
// disco; validadores customizados assumem a responsabilidade pelo tipo.
|
|
493
|
+
if (exists(candidate)) {
|
|
494
|
+
if (exists === fs.existsSync && !fs.statSync(candidate).isDirectory()) throw new Error('not-directory');
|
|
495
|
+
return /^[A-Za-z]:[\\/]/.test(candidate) ? path.win32.normalize(candidate) : path.resolve(candidate);
|
|
496
|
+
}
|
|
483
497
|
} catch (_) {}
|
|
484
498
|
const cut = candidate.lastIndexOf(' ');
|
|
485
499
|
if (cut < 0) break;
|
|
@@ -7,14 +7,18 @@ const crypto = require('crypto');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
9
|
function workspaceScope(cwd = process.cwd(), kind = 'chat') {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const raw = String(cwd || process.cwd());
|
|
11
|
+
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(raw) || /^\\\\/.test(raw);
|
|
12
|
+
const absolute = isWindowsPath ? path.win32.resolve(raw) : path.resolve(raw);
|
|
13
|
+
const normalized = (process.platform === 'win32' || isWindowsPath) ? absolute.toLowerCase() : absolute;
|
|
12
14
|
const digest = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
|
|
13
15
|
return `${String(kind || 'chat').toLowerCase()}:${digest}`;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
function conversationTitle(cwd = process.cwd(), kind = 'CLI') {
|
|
17
|
-
const
|
|
19
|
+
const raw = String(cwd || process.cwd());
|
|
20
|
+
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(raw) || /^\\\\/.test(raw);
|
|
21
|
+
const name = (isWindowsPath ? path.win32.basename(path.win32.resolve(raw)) : path.basename(path.resolve(raw))) || 'início';
|
|
18
22
|
return `${kind} · ${name}`.slice(0, 120);
|
|
19
23
|
}
|
|
20
24
|
|
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
|
|
@@ -155,7 +158,7 @@ function classifyError(msg) {
|
|
|
155
158
|
// A classe precisa sobreviver a isso: o recovery correto depende dela e não
|
|
156
159
|
// pode cair em unknown só por causa do encoding do terminal.
|
|
157
160
|
if (/is not recognized as (an )?(internal|the name of)|command not found|no such command|n[^a-z\s]{0,3}o (?:foi )?encontrad[oa] o comando|n[^a-z\s]{0,3}o [^\r\n]{0,80}reconhecid[oa]|invalid switch|unrecognized option|syntax of the command is incorrect|unexpectedtoken|parsererror|terminator.*string/i.test(s)) return 'invalid_command';
|
|
158
|
-
if (/working directory|invalid directory|directory name is invalid|system cannot find the path specified|n[^a-z\s]{0,3}o pode encontrar o caminho especificado|enoent.*(?:spawn|chdir)|chdir.*enoent/i.test(s)) return 'invalid_workdir';
|
|
161
|
+
if (/working directory|invalid directory|directory name is invalid|system cannot find the path specified|n[^a-z\s]{0,3}o pode encontrar o caminho especificado|enoent.*(?:spawn|chdir)|chdir.*enoent|(?:can(?:not|'t)|could not) cd\b|\bcd: .*no such (?:file|directory)/i.test(s)) return 'invalid_workdir';
|
|
159
162
|
if (/enoent|no such file|não encontrad|not found|arquivo inexistente|404/i.test(s)) return 'not_found';
|
|
160
163
|
if (/etimedout|timed out|timeout|excedeu o tempo/i.test(s)) return 'timeout';
|
|
161
164
|
if (/econnrefused|enotfound|getaddrinfo|network|connection refused|conexão recusada|conn|dns/i.test(s)) return 'network';
|
|
@@ -226,7 +229,7 @@ function classifyToolResult(raw) {
|
|
|
226
229
|
const exitCode = exit != null && Number.isFinite(Number(exit)) ? Number(exit) : -1;
|
|
227
230
|
const hadStderr = !!(raw.stderr && String(raw.stderr).trim());
|
|
228
231
|
if (failed) {
|
|
229
|
-
const errorClass = classifyError(errMsg);
|
|
232
|
+
const errorClass = exitCode === 127 ? 'invalid_command' : classifyError(errMsg);
|
|
230
233
|
return {
|
|
231
234
|
ok: false,
|
|
232
235
|
status: errorClass === 'blocked' ? 'blocked' : 'failed',
|
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) {
|
package/lib/intelligence-core.js
CHANGED
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
|
|
11
|
-
const INTELLIGENCE_CONTRACT =
|
|
12
|
-
|
|
13
|
-
// catalogFreshness appends an ISO time before parsing this value.
|
|
14
|
-
const PRICE_REVISION = '2026-08-11';
|
|
11
|
+
const INTELLIGENCE_CONTRACT = 3;
|
|
12
|
+
const PRICE_REVISION = '2026-09-13';
|
|
15
13
|
|
|
16
14
|
const MODEL_CATALOG = Object.freeze({
|
|
17
15
|
smart: {
|
|
@@ -23,6 +21,11 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
23
21
|
provider: 'deepseek', contextWindow: 1048576, price: { input: 0.14, output: 0.28, cachedInput: 0.0028 },
|
|
24
22
|
capabilities: ['tools', 'code', 'planning', 'execution', 'long-context'], toolReliability: 0.94, quality: 0.86,
|
|
25
23
|
},
|
|
24
|
+
'deepseek-v4.1-flash': {
|
|
25
|
+
upstreamId: 'deepseek/deepseek-v4.1-flash',
|
|
26
|
+
provider: 'openrouter', contextWindow: 1048576, price: { input: 0.30, output: 1.20, cachedInput: 0.006 },
|
|
27
|
+
capabilities: ['tools', 'vision', 'code', 'planning', 'execution', 'review', 'classification', 'summarization', 'long-context'], toolReliability: 0.95, quality: 0.92,
|
|
28
|
+
},
|
|
26
29
|
'deepseek-chat': {
|
|
27
30
|
upstreamId: 'deepseek/deepseek-chat',
|
|
28
31
|
provider: 'deepseek', contextWindow: 163840, price: { input: 0.2002, output: 0.8001, cachedInput: 0.2002 },
|
|
@@ -63,6 +66,11 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
63
66
|
provider: 'zai', contextWindow: 1048576, price: { input: 0.76, output: 2.42, cachedInput: 0.152 },
|
|
64
67
|
capabilities: ['tools', 'planning', 'code', 'long-context'], toolReliability: 0.88, quality: 0.89,
|
|
65
68
|
},
|
|
69
|
+
'glm-5.3': {
|
|
70
|
+
upstreamId: 'z-ai/glm-5.3',
|
|
71
|
+
provider: 'openrouter', contextWindow: 1310720, price: { input: 1.40, output: 4.40, cachedInput: 0.26 },
|
|
72
|
+
capabilities: ['tools', 'planning', 'code', 'review', 'long-context'], toolReliability: 0.91, quality: 0.92,
|
|
73
|
+
},
|
|
66
74
|
'mimo-v2.5': {
|
|
67
75
|
upstreamId: 'xiaomi/mimo-v2.5',
|
|
68
76
|
provider: 'xiaomi', contextWindow: 1050000, price: { input: 0.14, output: 0.28, cachedInput: 0.0028 },
|
|
@@ -104,8 +112,6 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
104
112
|
capabilities: ['tools', 'code', 'planning', 'review'], toolReliability: 0.96, quality: 0.95,
|
|
105
113
|
},
|
|
106
114
|
'claude-haiku-4-5': {
|
|
107
|
-
// ID canônico exposto pelo OpenRouter. O alias interno permanece estável
|
|
108
|
-
// para planos e histórico, mas o gateway precisa receber este identificador.
|
|
109
115
|
upstreamId: 'anthropic/claude-4.5-haiku-20251001',
|
|
110
116
|
provider: 'anthropic', contextWindow: 200000, price: { input: 1.00, output: 5.00, cachedInput: 0.10 },
|
|
111
117
|
capabilities: ['tools', 'code', 'planning', 'execution', 'review', 'vision'], toolReliability: 0.94, quality: 0.88,
|
|
@@ -171,62 +177,79 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
171
177
|
});
|
|
172
178
|
|
|
173
179
|
const MODEL_ALIASES = Object.freeze({
|
|
174
|
-
deepseek: 'deepseek-v4-flash',
|
|
175
|
-
'deepseek-v4': 'deepseek-v4-flash',
|
|
180
|
+
deepseek: 'deepseek-v4.1-flash',
|
|
181
|
+
'deepseek-v4.1': 'deepseek-v4.1-flash',
|
|
182
|
+
'deepseek-4.1': 'deepseek-v4.1-flash',
|
|
183
|
+
'deepseek-v4': 'deepseek-v4-pro',
|
|
176
184
|
glm: 'glm-4.6',
|
|
177
185
|
mimo: 'mimo-v2.5',
|
|
178
|
-
'gemini-lite': 'gemini-2.5-flash
|
|
186
|
+
'gemini-lite': 'gemini-2.5-flash',
|
|
179
187
|
'claude-haiku': 'claude-haiku-4-5',
|
|
180
188
|
});
|
|
181
189
|
|
|
182
|
-
//
|
|
183
|
-
// de
|
|
190
|
+
// Modelos aposentados do gateway do produto. Eles PERMANECEM no catalogo porque
|
|
191
|
+
// historico de consumo antigo ainda precisa ser precificado com o preco que
|
|
192
|
+
// valia na epoca; o que eles nao podem mais e entrar em contrato ativo ou em
|
|
193
|
+
// roteamento. A conversao mora AQUI, em cada superficie, e nao na reescrita que
|
|
194
|
+
// o backend faz no gate do proxy: uma superficie que dependesse daquela correcao
|
|
195
|
+
// ficaria errada em offline, em BYOK e em qualquer caminho que nao passe pelo
|
|
196
|
+
// proxy — foi exatamente o caso do CLI, cuja cadeia de executor do plano Free
|
|
197
|
+
// apontava inteira para os dois ids abaixo.
|
|
198
|
+
const RETIRED_MODELS = Object.freeze({
|
|
199
|
+
'deepseek-v4-flash': 'deepseek-v4.1-flash',
|
|
200
|
+
'gemini-2.5-flash-lite': 'gemini-2.5-flash',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Equipe oficial do modo Automático. Papéis são deliberadamente poucos: a
|
|
204
|
+
// qualidade vem de especialização + provas objetivas, não de trocar de modelo
|
|
205
|
+
// a cada etapa. Modelos premium continuam fora desta cascata até validação real.
|
|
184
206
|
const OFFICIAL_AUTOMATIC_TEAM = Object.freeze({
|
|
185
207
|
planner: Object.freeze({ model: 'gpt-5.6-luna', label: 'Luna', responsibility: 'planejamento' }),
|
|
186
208
|
executor: Object.freeze({ model: 'deepseek-v4-pro', label: 'DeepSeek Pro', responsibility: 'execução' }),
|
|
187
209
|
corrector: Object.freeze({ model: 'deepseek-v4-pro', label: 'DeepSeek Pro', responsibility: 'correção' }),
|
|
188
|
-
multimodal: Object.freeze({ model: '
|
|
210
|
+
multimodal: Object.freeze({ model: 'deepseek-v4.1-flash', fallback: 'gpt-5.6-luna', label: 'DeepSeek V4.1', responsibility: 'imagem, arquivo e documento visual' }),
|
|
189
211
|
verifier: Object.freeze({ model: null, label: 'Verificadores determinísticos', responsibility: 'prova objetiva de entrega' }),
|
|
190
212
|
});
|
|
191
213
|
|
|
192
214
|
// Contrato dos agentes funcionais. Isto não é fine-tuning: cada papel recebe um
|
|
193
|
-
// prompt curto e continua sujeito
|
|
215
|
+
// prompt curto e continua sujeito às provas das ferramentas. Para planos pagos,
|
|
216
|
+
// a equipe oficial não muda sem uma falha objetiva registrada.
|
|
194
217
|
const AGENT_ROLES = Object.freeze({
|
|
195
218
|
classifier: Object.freeze({
|
|
196
219
|
prompt: 'Classifique a intenção e devolva somente a estrutura pedida. Não execute ferramentas nem invente dados.',
|
|
197
|
-
models: Object.freeze({ free: ['
|
|
220
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4.1-flash', 'gpt-5.6-luna'] }),
|
|
198
221
|
}),
|
|
199
222
|
planner: Object.freeze({
|
|
200
223
|
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: ['
|
|
224
|
+
models: Object.freeze({ free: ['gpt-5.6-luna', 'deepseek-v4.1-flash'], basic: ['gpt-5.6-luna', 'deepseek-v4.1-flash', 'deepseek-v4-pro'], pro: ['gpt-5.6-luna', 'glm-5.3', 'deepseek-v4-pro'] }),
|
|
202
225
|
}),
|
|
203
226
|
executor: Object.freeze({
|
|
204
227
|
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-flash', '
|
|
228
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'] }),
|
|
206
229
|
}),
|
|
207
230
|
inspector: Object.freeze({
|
|
208
231
|
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: ['
|
|
232
|
+
models: Object.freeze({ free: ['gpt-5.6-luna', 'deepseek-v4.1-flash'], basic: ['gpt-5.6-luna', 'deepseek-v4.1-flash'], pro: ['glm-5.3', 'gpt-5.6-luna', 'deepseek-v4-pro'] }),
|
|
210
233
|
}),
|
|
211
234
|
corrector: Object.freeze({
|
|
212
235
|
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-flash', '
|
|
236
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'] }),
|
|
214
237
|
}),
|
|
215
238
|
vision: Object.freeze({
|
|
216
239
|
prompt: 'Extraia fatos observáveis da imagem e separe leitura, inferência e incerteza. Não invente texto ilegível.',
|
|
217
|
-
models: Object.freeze({ free: ['
|
|
240
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4.1-flash', 'gpt-5.6-luna', 'mimo-v2.5'], pro: ['deepseek-v4.1-flash', 'gpt-5.6-luna', 'mimo-v2.5'] }),
|
|
218
241
|
}),
|
|
219
242
|
document: Object.freeze({
|
|
220
243
|
prompt: 'Produza conteúdo estruturado e use o motor nativo do formato solicitado. Preserve editabilidade e valide o arquivo gerado.',
|
|
221
|
-
models: Object.freeze({ free: ['
|
|
244
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4.1-flash', 'gpt-5.6-luna', 'mimo-v2.5'], pro: ['deepseek-v4.1-flash', 'gpt-5.6-luna', 'mimo-v2.5'] }),
|
|
222
245
|
}),
|
|
223
246
|
site_builder: Object.freeze({
|
|
224
247
|
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-flash'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
248
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'deepseek-v4.1-flash'] }),
|
|
226
249
|
}),
|
|
227
250
|
cloud_box: Object.freeze({
|
|
228
251
|
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-flash'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
252
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'deepseek-v4.1-flash'] }),
|
|
230
253
|
}),
|
|
231
254
|
});
|
|
232
255
|
|
|
@@ -256,6 +279,14 @@ function normalizeModelId(model) {
|
|
|
256
279
|
return upstream ? upstream[0] : transportFree;
|
|
257
280
|
}
|
|
258
281
|
|
|
282
|
+
// Camada de compatibilidade: recebe qualquer id (alias, id do upstream, id
|
|
283
|
+
// aposentado) e devolve um id que o produto ainda serve. Use isto — e nao
|
|
284
|
+
// normalizeModelId — em qualquer ponto que va CHAMAR um modelo.
|
|
285
|
+
function supportedModelId(model) {
|
|
286
|
+
const id = normalizeModelId(model);
|
|
287
|
+
return RETIRED_MODELS[id] || id;
|
|
288
|
+
}
|
|
289
|
+
|
|
259
290
|
function modelInfo(model, now) {
|
|
260
291
|
const id = normalizeModelId(model);
|
|
261
292
|
const info = MODEL_CATALOG[id] || {
|
|
@@ -368,9 +399,6 @@ function _safeModelId(value) {
|
|
|
368
399
|
return MODEL_CATALOG[normalized] || normalized === 'smart' ? normalized : _safeToolName(value);
|
|
369
400
|
}
|
|
370
401
|
|
|
371
|
-
// Contrato compacto para painéis e comparação de execuções. É deliberadamente
|
|
372
|
-
// agregado: o painel sabe se a missão funcionou e onde falhou, sem receber o
|
|
373
|
-
// pedido do usuário, conteúdo de e-mail, arquivo, comando ou diretório.
|
|
374
402
|
function buildOperationalSummary(input) {
|
|
375
403
|
const x = input || {};
|
|
376
404
|
const usage = x.tokens || x.usage || {};
|
|
@@ -391,21 +419,12 @@ function buildOperationalSummary(input) {
|
|
|
391
419
|
}
|
|
392
420
|
const verification = x.verification || {};
|
|
393
421
|
return {
|
|
394
|
-
version: 1,
|
|
395
|
-
surface: _safeToolName(x.surface || 'cli'),
|
|
396
|
-
model: _safeModelId(x.model || 'smart'),
|
|
422
|
+
version: 1, surface: _safeToolName(x.surface || 'cli'), model: _safeModelId(x.model || 'smart'),
|
|
397
423
|
status: x.completion && x.completion.ok ? 'completed' : (x.guard ? 'stopped_by_guard' : 'blocked'),
|
|
398
424
|
completionReason: _safeToolName(x.completion && x.completion.reason || 'unknown'),
|
|
399
|
-
steps: Math.max(0, Number(x.steps) || 0),
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
tokens: {
|
|
403
|
-
input: Math.max(0, Number(usage.inTok || usage.input_tokens) || 0),
|
|
404
|
-
output: Math.max(0, Number(usage.outTok || usage.output_tokens) || 0),
|
|
405
|
-
cached: Math.max(0, Number(usage.cachedTok || usage.cacheTok || usage.cached_tokens) || 0),
|
|
406
|
-
},
|
|
407
|
-
actions,
|
|
408
|
-
failures: [...failures.values()].sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 20),
|
|
425
|
+
steps: Math.max(0, Number(x.steps) || 0), durationMs: Math.max(0, Number(x.durationMs) || 0), credits: Math.max(0, Number(x.credits) || 0),
|
|
426
|
+
tokens: { input: Math.max(0, Number(usage.inTok || usage.input_tokens) || 0), output: Math.max(0, Number(usage.outTok || usage.output_tokens) || 0), cached: Math.max(0, Number(usage.cachedTok || usage.cacheTok || usage.cached_tokens) || 0) },
|
|
427
|
+
actions, failures: [...failures.values()].sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 20),
|
|
409
428
|
verification: { total: Math.max(0, Number(verification.total) || 0), passed: Math.max(0, Number(verification.passed) || 0), allOk: verification.allOk === true },
|
|
410
429
|
cache: Object.assign({ hits: 0, misses: 0, entries: 0 }, x.missionCache || {}),
|
|
411
430
|
};
|
|
@@ -484,13 +503,28 @@ function agentRoleContract(role, plan, options) {
|
|
|
484
503
|
const id = Object.prototype.hasOwnProperty.call(AGENT_ROLES, role) ? role : 'executor';
|
|
485
504
|
const planId = normalizeAgentPlan(plan);
|
|
486
505
|
const spec = AGENT_ROLES[id];
|
|
506
|
+
const complexTeams = {
|
|
507
|
+
free: {
|
|
508
|
+
planner: ['gpt-5.6-luna', 'deepseek-v4.1-flash'],
|
|
509
|
+
executor: ['deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
510
|
+
corrector: ['deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
511
|
+
},
|
|
512
|
+
basic: {
|
|
513
|
+
planner: ['gpt-5.6-luna', 'deepseek-v4.1-flash', 'deepseek-v4-pro'],
|
|
514
|
+
executor: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
515
|
+
corrector: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
516
|
+
},
|
|
517
|
+
pro: {
|
|
518
|
+
planner: ['gpt-5.6-luna', 'glm-5.3', 'deepseek-v4-pro'],
|
|
519
|
+
executor: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
520
|
+
corrector: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'],
|
|
521
|
+
},
|
|
522
|
+
};
|
|
487
523
|
const complexCore = options && options.complex === true && ['planner', 'executor', 'corrector'].includes(id)
|
|
488
|
-
?
|
|
489
|
-
? ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro']
|
|
490
|
-
: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'])
|
|
524
|
+
? complexTeams[planId][id]
|
|
491
525
|
: null;
|
|
492
526
|
const requested = complexCore || spec.models[planId] || spec.models.free;
|
|
493
|
-
const allowedRaw =
|
|
527
|
+
const allowedRaw = options && Array.isArray(options.allowedModels) ? options.allowedModels : null;
|
|
494
528
|
const allowAll = !allowedRaw || allowedRaw.includes('*');
|
|
495
529
|
const allowed = allowAll ? null : new Set(allowedRaw.map(normalizeModelId));
|
|
496
530
|
const candidates = requested.map(normalizeModelId).filter(model => model === 'smart' || MODEL_CATALOG[model])
|
|
@@ -512,7 +546,11 @@ function routeModel(task, reliability) {
|
|
|
512
546
|
if (t.kind === 'code') required.add('code');
|
|
513
547
|
if (t.kind === 'review') required.add('review');
|
|
514
548
|
if (t.kind === 'vision') required.add('vision');
|
|
515
|
-
|
|
549
|
+
// Era um Set escrito a mao aqui dentro. Agora le a MESMA tabela que o CLI e
|
|
550
|
+
// o App leem, para os tres nao divergirem de novo.
|
|
551
|
+
const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(supportedModelId)
|
|
552
|
+
.filter(id => MODEL_CATALOG[id] && id !== 'smart')
|
|
553
|
+
.filter((id, i, list) => list.indexOf(id) === i);
|
|
516
554
|
const rel = reliability || {};
|
|
517
555
|
const plan = String(t.plan || '').toLowerCase();
|
|
518
556
|
const isPaid = plan && plan !== 'free' && plan !== 'gratuito';
|
|
@@ -527,12 +565,13 @@ function routeModel(task, reliability) {
|
|
|
527
565
|
const score = (observed * 0.35) + (m.quality * qualityWeight) + ((1 / (1 + price)) * costWeight) - (missing * 2);
|
|
528
566
|
return { id, score: +score.toFixed(6), missing };
|
|
529
567
|
}).sort((a, b) => b.score - a.score);
|
|
530
|
-
return { model: scored[0] ? scored[0].id :
|
|
568
|
+
return { model: scored[0] ? scored[0].id : 'deepseek-v4-pro', ranked: scored };
|
|
531
569
|
}
|
|
532
570
|
|
|
533
571
|
module.exports = {
|
|
534
572
|
INTELLIGENCE_CONTRACT, PRICE_REVISION, MODEL_CATALOG, MODEL_ALIASES, DEFAULT_PRICE,
|
|
535
573
|
MEMORY_TYPES, MEMORY_TRUST, CONTEXT_SOURCES, OFFICIAL_AUTOMATIC_TEAM, AGENT_ROLES,
|
|
574
|
+
RETIRED_MODELS, supportedModelId,
|
|
536
575
|
normalizeModelId, modelInfo, legacyPriceMap, normalizeUsage, estimateCostUsd, catalogFreshness,
|
|
537
576
|
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff, cleanOperationalText, buildOperationalSummary,
|
|
538
577
|
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/lib/tools.js
CHANGED
|
@@ -546,7 +546,8 @@ function _commandScopeViolation(cmd, confineDir) {
|
|
|
546
546
|
// `findstr /c:"texto"`. Não são caminhos Unix nem tentativas de escapar da
|
|
547
547
|
// raiz. Sem esta exceção uma busca contendo `</script>` era lida como
|
|
548
548
|
// redirecionamento e o `/c:` seguinte virava um falso caminho externo.
|
|
549
|
-
if (
|
|
549
|
+
if (/^\/[a-z](?::|$)/i.test(raw)
|
|
550
|
+
&& (process.platform === 'win32' || /(?:^|[&|]\s*)findstr\b/i.test(text))) continue;
|
|
550
551
|
const abs = path.resolve(root, raw);
|
|
551
552
|
if (!_scopeContains(root, abs)) {
|
|
552
553
|
return `CAPABILITY_DENIED: comando mutante referencia "${raw}", fora da raiz autorizada "${root}".`;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminal-smart-cli",
|
|
3
|
-
"version": "0.97.
|
|
3
|
+
"version": "0.97.68",
|
|
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",
|