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 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
- return id === 'pro' ? 2 : (id === 'basic' ? 1 : 0);
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
- const first = String(selectedModel || DEFAULT_EXECUTOR).trim();
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
- // O roteador pode selecionar um executor mais específico que o contrato padrão.
100
- // Só o preservamos se ele já estiver autorizado pelo papel/plano; um id externo
101
- // ou comercialmente não contratado não pode contornar o catálogo do produto.
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((candidates.includes(first) ? [first, ...candidates] : candidates))]
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
- if (exists(candidate) && fs.statSync(candidate).isDirectory()) return path.resolve(candidate);
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 absolute = path.resolve(cwd || process.cwd());
11
- const normalized = process.platform === 'win32' ? absolute.toLowerCase() : absolute;
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 name = path.basename(path.resolve(cwd || process.cwd())) || 'início';
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
- const DEFAULT_EXECUTOR = 'deepseek-v4-flash'; // executor padrão (venceu o bake-off de código)
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
- const DEFAULT_JUDGE = 'deepseek-v4-flash';
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) {
@@ -8,10 +8,8 @@
8
8
 
9
9
  const crypto = require('crypto');
10
10
 
11
- const INTELLIGENCE_CONTRACT = 2;
12
- // Keep this machine-readable. Human/provider notes belong in commit history;
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-lite',
186
+ 'gemini-lite': 'gemini-2.5-flash',
179
187
  'claude-haiku': 'claude-haiku-4-5',
180
188
  });
181
189
 
182
- // Equipe oficial do modo Automático: especialização e prova objetiva vêm antes
183
- // de cascatas longas de modelos. Premium fica fora até validação real.
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: 'mimo-v2.5', fallback: 'gemini-2.5-flash', label: 'MiMo', responsibility: 'imagem, arquivo e documento visual' }),
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 à prova das ferramentas.
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: ['gemini-2.5-flash-lite'], basic: ['gemini-2.5-flash-lite', 'gpt-4o-mini'], pro: ['gemini-2.5-flash-lite', 'gpt-4o-mini'] }),
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: ['deepseek-v4-flash', 'gemini-2.5-flash-lite'], basic: ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro'], pro: ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro'] }),
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', 'gemini-2.5-flash-lite'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'] }),
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: ['gemini-2.5-flash-lite'], basic: ['gpt-5.6-luna', 'glm-5.2'], pro: ['gpt-5.6-luna', 'glm-5.2'] }),
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', 'gemini-2.5-flash-lite'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'] }),
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: ['gemini-2.5-flash'], basic: ['mimo-v2.5', 'gemini-2.5-flash'], pro: ['mimo-v2.5', 'gemini-2.5-flash', 'gpt-5.6-luna'] }),
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: ['gemini-2.5-flash'], basic: ['mimo-v2.5', 'gemini-2.5-flash'], pro: ['mimo-v2.5', 'gemini-2.5-flash'] }),
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
- durationMs: Math.max(0, Number(x.durationMs) || 0),
401
- credits: Math.max(0, Number(x.credits) || 0),
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
- ? (id === 'planner'
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 = complexCore && planId === 'free' ? null : (options && Array.isArray(options.allowedModels) ? options.allowedModels : null);
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
- const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(normalizeModelId).filter(id => MODEL_CATALOG[id] && id !== 'smart');
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 : (isPaid ? 'deepseek-v4-pro' : 'deepseek-v4-flash'), ranked: scored };
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
- model: keyring.modeloPara(k, 'gemini-2.5-flash-lite'), stream: false, max_completion_tokens: 5,
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: 'gemini-2.5-flash-lite', inTok: u.prompt_tokens || 0, outTok: u.completion_tokens || 0 } }).catch(() => {});
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 (process.platform === 'win32' && /^\/[a-z](?::|$)/i.test(raw)) continue;
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.66",
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 test/durable-operation.test.js && node test/mission-tool-cache.test.js && node test/content-quality-runtime-files.test.js && node test/personal-agent-llm.test.js && node test/personal-inspector-efficiency.test.js && node test/personal-inspector-context.test.js && node test/planner-fallback.test.js && node test/cli-agent-regressions.test.js && node test/harness-hardening.test.js && node test/evolution-telemetry.test.js"
9
+ "test": "node scripts/run-tests.js"
10
10
  },
11
11
  "files": [
12
12
  "bin",