terminal-smart-cli 0.97.67 → 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
@@ -103,11 +103,12 @@ function executorFallbackChain({ selectedModel, forced = false, source = 'cloud'
103
103
  const first = intelligence.supportedModelId(String(selectedModel || DEFAULT_EXECUTOR).trim());
104
104
  if (forced) return [first];
105
105
  const role = intelligence.agentRoleContract('executor', plan, { complex });
106
- // O roteador pode selecionar um executor mais específico que o contrato padrão.
107
- // Só o preservamos se ele já estiver autorizado pelo papel/plano; um id externo
108
- // 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.
109
110
  const candidates = role.candidates.filter(Boolean);
110
- return [...new Set((candidates.includes(first) ? [first, ...candidates] : candidates))]
111
+ return [...new Set(candidates)]
111
112
  .slice(0, 1 + planUpgradeLimit(plan));
112
113
  }
113
114
 
@@ -486,7 +487,13 @@ function explicitTaskWorkdir(taskText, exists = fs.existsSync) {
486
487
  // existente, preservando nomes de diretório que tenham espaços.
487
488
  while (candidate) {
488
489
  try {
489
- 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
+ }
490
497
  } catch (_) {}
491
498
  const cut = candidate.lastIndexOf(' ');
492
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
@@ -158,7 +158,7 @@ function classifyError(msg) {
158
158
  // A classe precisa sobreviver a isso: o recovery correto depende dela e não
159
159
  // pode cair em unknown só por causa do encoding do terminal.
160
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';
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/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';
162
162
  if (/enoent|no such file|não encontrad|not found|arquivo inexistente|404/i.test(s)) return 'not_found';
163
163
  if (/etimedout|timed out|timeout|excedeu o tempo/i.test(s)) return 'timeout';
164
164
  if (/econnrefused|enotfound|getaddrinfo|network|connection refused|conexão recusada|conn|dns/i.test(s)) return 'network';
@@ -229,7 +229,7 @@ function classifyToolResult(raw) {
229
229
  const exitCode = exit != null && Number.isFinite(Number(exit)) ? Number(exit) : -1;
230
230
  const hadStderr = !!(raw.stderr && String(raw.stderr).trim());
231
231
  if (failed) {
232
- const errorClass = classifyError(errMsg);
232
+ const errorClass = exitCode === 127 ? 'invalid_command' : classifyError(errMsg);
233
233
  return {
234
234
  ok: false,
235
235
  status: errorClass === 'blocked' ? 'blocked' : 'failed',
@@ -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,7 +177,9 @@ const MODEL_CATALOG = Object.freeze({
171
177
  });
172
178
 
173
179
  const MODEL_ALIASES = Object.freeze({
174
- deepseek: 'deepseek-v4-pro',
180
+ deepseek: 'deepseek-v4.1-flash',
181
+ 'deepseek-v4.1': 'deepseek-v4.1-flash',
182
+ 'deepseek-4.1': 'deepseek-v4.1-flash',
175
183
  'deepseek-v4': 'deepseek-v4-pro',
176
184
  glm: 'glm-4.6',
177
185
  mimo: 'mimo-v2.5',
@@ -188,58 +196,60 @@ const MODEL_ALIASES = Object.freeze({
188
196
  // proxy — foi exatamente o caso do CLI, cuja cadeia de executor do plano Free
189
197
  // apontava inteira para os dois ids abaixo.
190
198
  const RETIRED_MODELS = Object.freeze({
191
- 'deepseek-v4-flash': 'deepseek-v4-pro',
199
+ 'deepseek-v4-flash': 'deepseek-v4.1-flash',
192
200
  'gemini-2.5-flash-lite': 'gemini-2.5-flash',
193
201
  });
194
202
 
195
- // Equipe oficial do modo Automático: especialização e prova objetiva vêm antes
196
- // de cascatas longas de modelos. Premium fica fora até validação real.
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.
197
206
  const OFFICIAL_AUTOMATIC_TEAM = Object.freeze({
198
207
  planner: Object.freeze({ model: 'gpt-5.6-luna', label: 'Luna', responsibility: 'planejamento' }),
199
208
  executor: Object.freeze({ model: 'deepseek-v4-pro', label: 'DeepSeek Pro', responsibility: 'execução' }),
200
209
  corrector: Object.freeze({ model: 'deepseek-v4-pro', label: 'DeepSeek Pro', responsibility: 'correção' }),
201
- 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' }),
202
211
  verifier: Object.freeze({ model: null, label: 'Verificadores determinísticos', responsibility: 'prova objetiva de entrega' }),
203
212
  });
204
213
 
205
214
  // Contrato dos agentes funcionais. Isto não é fine-tuning: cada papel recebe um
206
- // 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.
207
217
  const AGENT_ROLES = Object.freeze({
208
218
  classifier: Object.freeze({
209
219
  prompt: 'Classifique a intenção e devolva somente a estrutura pedida. Não execute ferramentas nem invente dados.',
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'] }),
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'] }),
211
221
  }),
212
222
  planner: Object.freeze({
213
223
  prompt: 'Decomponha o objetivo em etapas executáveis, dependências, riscos, critérios de aceite e provas. Não execute a tarefa.',
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'] }),
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'] }),
215
225
  }),
216
226
  executor: Object.freeze({
217
227
  prompt: 'Execute somente a etapa recebida com as ferramentas autorizadas. Não declare sucesso sem resultado verificável.',
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'] }),
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'] }),
219
229
  }),
220
230
  inspector: Object.freeze({
221
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.',
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'] }),
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'] }),
223
233
  }),
224
234
  corrector: Object.freeze({
225
235
  prompt: 'Corrija apenas falhas confirmadas pelo inspetor, preserve o que funciona e repita as provas afetadas. Não amplie o escopo.',
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'] }),
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'] }),
227
237
  }),
228
238
  vision: Object.freeze({
229
239
  prompt: 'Extraia fatos observáveis da imagem e separe leitura, inferência e incerteza. Não invente texto ilegível.',
230
- 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'] }),
231
241
  }),
232
242
  document: Object.freeze({
233
243
  prompt: 'Produza conteúdo estruturado e use o motor nativo do formato solicitado. Preserve editabilidade e valide o arquivo gerado.',
234
- 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'] }),
235
245
  }),
236
246
  site_builder: Object.freeze({
237
247
  prompt: 'Crie ou edite o site completo, responsivo e acessível. Preserve o existente quando editar e valide a URL publicada.',
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'] }),
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'] }),
239
249
  }),
240
250
  cloud_box: Object.freeze({
241
251
  prompt: 'Opere a box com mudanças mínimas, registre comandos e valide serviço, porta e URL. Nunca exponha segredos.',
242
- models: Object.freeze({ free: ['deepseek-v4-pro', 'gpt-5.6-luna'], 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'] }),
243
253
  }),
244
254
  });
245
255
 
@@ -389,9 +399,6 @@ function _safeModelId(value) {
389
399
  return MODEL_CATALOG[normalized] || normalized === 'smart' ? normalized : _safeToolName(value);
390
400
  }
391
401
 
392
- // Contrato compacto para painéis e comparação de execuções. É deliberadamente
393
- // agregado: o painel sabe se a missão funcionou e onde falhou, sem receber o
394
- // pedido do usuário, conteúdo de e-mail, arquivo, comando ou diretório.
395
402
  function buildOperationalSummary(input) {
396
403
  const x = input || {};
397
404
  const usage = x.tokens || x.usage || {};
@@ -412,21 +419,12 @@ function buildOperationalSummary(input) {
412
419
  }
413
420
  const verification = x.verification || {};
414
421
  return {
415
- version: 1,
416
- surface: _safeToolName(x.surface || 'cli'),
417
- model: _safeModelId(x.model || 'smart'),
422
+ version: 1, surface: _safeToolName(x.surface || 'cli'), model: _safeModelId(x.model || 'smart'),
418
423
  status: x.completion && x.completion.ok ? 'completed' : (x.guard ? 'stopped_by_guard' : 'blocked'),
419
424
  completionReason: _safeToolName(x.completion && x.completion.reason || 'unknown'),
420
- steps: Math.max(0, Number(x.steps) || 0),
421
- durationMs: Math.max(0, Number(x.durationMs) || 0),
422
- credits: Math.max(0, Number(x.credits) || 0),
423
- tokens: {
424
- input: Math.max(0, Number(usage.inTok || usage.input_tokens) || 0),
425
- output: Math.max(0, Number(usage.outTok || usage.output_tokens) || 0),
426
- cached: Math.max(0, Number(usage.cachedTok || usage.cacheTok || usage.cached_tokens) || 0),
427
- },
428
- actions,
429
- 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),
430
428
  verification: { total: Math.max(0, Number(verification.total) || 0), passed: Math.max(0, Number(verification.passed) || 0), allOk: verification.allOk === true },
431
429
  cache: Object.assign({ hits: 0, misses: 0, entries: 0 }, x.missionCache || {}),
432
430
  };
@@ -505,13 +503,28 @@ function agentRoleContract(role, plan, options) {
505
503
  const id = Object.prototype.hasOwnProperty.call(AGENT_ROLES, role) ? role : 'executor';
506
504
  const planId = normalizeAgentPlan(plan);
507
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
+ };
508
523
  const complexCore = options && options.complex === true && ['planner', 'executor', 'corrector'].includes(id)
509
- ? (id === 'planner'
510
- ? ['gpt-5.6-luna', 'glm-5.2', 'deepseek-v4-pro']
511
- : ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'])
524
+ ? complexTeams[planId][id]
512
525
  : null;
513
526
  const requested = complexCore || spec.models[planId] || spec.models.free;
514
- 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;
515
528
  const allowAll = !allowedRaw || allowedRaw.includes('*');
516
529
  const allowed = allowAll ? null : new Set(allowedRaw.map(normalizeModelId));
517
530
  const candidates = requested.map(normalizeModelId).filter(model => model === 'smart' || MODEL_CATALOG[model])
@@ -533,9 +546,8 @@ function routeModel(task, reliability) {
533
546
  if (t.kind === 'code') required.add('code');
534
547
  if (t.kind === 'review') required.add('review');
535
548
  if (t.kind === 'vision') required.add('vision');
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.
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.
539
551
  const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(supportedModelId)
540
552
  .filter(id => MODEL_CATALOG[id] && id !== 'smart')
541
553
  .filter((id, i, list) => list.indexOf(id) === i);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.97.67",
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"