terminal-smart-cli 0.97.65 → 0.97.66

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
@@ -2296,7 +2296,13 @@ async function run(task, opts = {}) {
2296
2296
  if (!_tr.ok && _tr.status !== 'blocked') {
2297
2297
  const _shellHint = name === 'executar_comando' ? commandRecoveryHint(input.comando, _tr.evidence) : '';
2298
2298
  if (_shellHint && result && typeof result === 'object') result = Object.assign({}, result, { _recovery: _shellHint });
2299
- _toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence });
2299
+ // O sinal estruturado vem do ToolResult tipado — exitCode, errno e hadStderr, que
2300
+ // o core ja calculava e descartava — mais o pathKind, derivado AQUI a partir dos
2301
+ // argumentos da ferramenta: so o resultado ('relative' | 'absolute') viaja, o
2302
+ // caminho em si nunca sai da maquina.
2303
+ _toolErrs.push({ tool: name, class: _tr.errorClass, retryable: _tr.retryable, evidence: _tr.evidence,
2304
+ exitCode: _tr.exitCode, errno: _tr.errno, hadStderr: _tr.hadStderr,
2305
+ pathKind: require('./evolution-telemetry').pathKindOf(input) });
2300
2306
  const _failureKey = failureFingerprint(name, _tr, result);
2301
2307
  const _equivalentFailures = (_failureCounts.get(_failureKey) || 0) + 1;
2302
2308
  _failureCounts.set(_failureKey, _equivalentFailures);
package/lib/core.js CHANGED
@@ -167,11 +167,44 @@ function classifyError(msg) {
167
167
  return 'unknown';
168
168
  }
169
169
 
170
- // raw = retorno de uma ferramenta (execute). Devolve { ok, status, errorClass, retryable, evidence }.
170
+ // raw = retorno de uma ferramenta (execute). Devolve
171
+ // { ok, status, errorClass, retryable, evidence, exitCode, errno, hadStderr }.
171
172
  // NÃO muta o raw. Convenções do TS: {erro} = falha; {codigo|exitCode}≠0 = falha de shell;
172
173
  // {written|created|resumo|stdout|...} = evidência de sucesso.
174
+ //
175
+ // Os três últimos campos entraram em 12/09/2026 e são o SINAL ESTRUTURADO que a telemetria
176
+ // do harness passou a aceitar. Eles já estavam aqui dentro: `exit` e `errMsg` eram
177
+ // calculados e descartados. Sem eles, três falhas com correções completamente diferentes
178
+ // chegavam ao banco como a mesma linha:
179
+ //
180
+ // ENOENT o arquivo não existe -> criar, ou corrigir o caminho
181
+ // EACCES existe, mas sem permissão -> permissão, ou outro caminho
182
+ // EISDIR pediram para ler um diretório -> usar listar_diretorio
183
+ //
184
+ // As três eram `not_found`. O mesmo vale para o código de saída: 127 é comando que não
185
+ // existe, 126 é arquivo sem permissão de execução, 1 é o programa rodou e reprovou.
186
+ //
187
+ // Nada aqui carrega conteúdo de usuário: errno é vocabulário do sistema operacional e sai
188
+ // de uma lista fechada, exitCode é um inteiro e hadStderr é booleano.
189
+ const ERRNOS_CONHECIDOS = new Set(['ENOENT', 'EACCES', 'EPERM', 'EISDIR', 'ENOTDIR', 'EEXIST',
190
+ 'ENOTEMPTY', 'ENOSPC', 'EROFS', 'EMFILE', 'ENFILE', 'ENAMETOOLONG', 'EBUSY', 'EIO', 'EINVAL',
191
+ 'EAGAIN', 'ETIMEDOUT', 'ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH', 'ENETUNREACH',
192
+ 'EADDRINUSE', 'ENOTFOUND', 'EAI_AGAIN']);
193
+
194
+ // Lista fechada, e não um regex solto: `\bE[A-Z]+\b` casaria com a palavra "ERROR", que
195
+ // aparece em quase toda mensagem de falha, e o campo viraria ruído em vez de sinal.
196
+ function extractErrno(msg) {
197
+ const candidatos = String(msg || '').toUpperCase().match(/\bE[A-Z_]{2,11}\b/g);
198
+ if (!candidatos) return '';
199
+ for (const c of candidatos) if (ERRNOS_CONHECIDOS.has(c)) return c;
200
+ return '';
201
+ }
202
+
173
203
  function classifyToolResult(raw) {
174
- if (raw == null) return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: '' };
204
+ if (raw == null) {
205
+ return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: '',
206
+ exitCode: -1, errno: '', hadStderr: false };
207
+ }
175
208
  // Alguns adaptadores legados retornam falhas como texto. Tratar todo texto como sucesso
176
209
  // permitia que "ERRO ao executar..." virasse etapa concluída. Mantém strings normais como
177
210
  // sucesso, mas reconhece prefixos inequívocos de falha/bloqueio.
@@ -181,13 +214,17 @@ function classifyToolResult(raw) {
181
214
  if (failedText) {
182
215
  const errorClass = classifyError(text);
183
216
  return { ok: false, status: errorClass === 'blocked' ? 'blocked' : 'failed', errorClass,
184
- retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300) };
217
+ retryable: RETRYABLE_CLASSES.has(errorClass), evidence: text.replace(/\s+/g, ' ').slice(0, 300),
218
+ exitCode: -1, errno: extractErrno(text), hadStderr: false };
185
219
  }
186
- return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200) };
220
+ return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: text.slice(0, 200),
221
+ exitCode: -1, errno: '', hadStderr: false };
187
222
  }
188
223
  const exit = raw.codigo != null ? raw.codigo : (raw.exitCode != null ? raw.exitCode : null);
189
224
  const errMsg = raw.erro || raw.error || (exit != null && exit !== 0 ? (raw.stderr || raw.stdout || ('exit ' + exit)) : '');
190
225
  const failed = !!raw.erro || !!raw.error || (exit != null && exit !== 0);
226
+ const exitCode = exit != null && Number.isFinite(Number(exit)) ? Number(exit) : -1;
227
+ const hadStderr = !!(raw.stderr && String(raw.stderr).trim());
191
228
  if (failed) {
192
229
  const errorClass = classifyError(errMsg);
193
230
  return {
@@ -196,10 +233,14 @@ function classifyToolResult(raw) {
196
233
  errorClass,
197
234
  retryable: RETRYABLE_CLASSES.has(errorClass),
198
235
  evidence: String(errMsg).replace(/\s+/g, ' ').slice(0, 300),
236
+ exitCode,
237
+ errno: extractErrno(errMsg),
238
+ hadStderr,
199
239
  };
200
240
  }
201
241
  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) };
242
+ return { ok: true, status: 'ok', errorClass: null, retryable: false, evidence: String(ev || '').replace(/\s+/g, ' ').slice(0, 200),
243
+ exitCode, errno: '', hadStderr };
203
244
  }
204
245
 
205
246
  // ── SEGREDOS: nunca imprimir chave/token na saída (B18) ───────────────────────
@@ -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 fingerprint = crypto.createHash('sha256').update([tool, errorClass, stage, pattern].join('|')).digest('hex');
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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.97.65",
3
+ "version": "0.97.66",
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"
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"
10
10
  },
11
11
  "files": [
12
12
  "bin",