terminal-smart-cli 0.97.29 → 0.97.31
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/bin/ts.js +73 -6
- package/lib/acp.js +40 -18
- package/lib/agent.js +522 -73
- package/lib/core.js +11 -4
- package/lib/eval.js +2 -2
- package/lib/evolution-proposal.js +47 -0
- package/lib/file-lock.js +42 -0
- package/lib/gateways.js +69 -0
- package/lib/intelligence-core.js +115 -33
- package/lib/meta.js +6 -2
- package/lib/owner-audit.js +112 -0
- package/lib/providers.js +4 -1
- package/lib/recovery.js +12 -0
- package/lib/tools.js +145 -25
- package/lib/ui.js +10 -0
- package/package.json +2 -2
package/lib/core.js
CHANGED
|
@@ -136,10 +136,10 @@ const EVENT_TYPES = ['system', 'tool', 'assistant', 'result'];
|
|
|
136
136
|
// raw (retrocompatível). Classe de erro alimenta o Recovery Engine e o ledger; `ok:false`
|
|
137
137
|
// deve barrar a marcação de sucesso a montante (meta/orquestrador).
|
|
138
138
|
const ERROR_CLASSES = ['permission_denied', 'not_found', 'port_in_use', 'missing_dep', 'package_lock',
|
|
139
|
-
'timeout', 'compile_error', 'test_failed', 'network', 'auth', 'git_conflict', 'invalid_schema',
|
|
140
|
-
'blocked', 'unknown'];
|
|
139
|
+
'timeout', 'compile_error', 'test_failed', 'network', 'auth', 'git_conflict', 'invalid_schema', 'resource_locked',
|
|
140
|
+
'invalid_command', 'invalid_workdir', 'blocked', 'unknown'];
|
|
141
141
|
// classes transitórias → vale re-tentar (mesma abordagem); as demais exigem mudar de abordagem.
|
|
142
|
-
const RETRYABLE_CLASSES = new Set(['network', 'timeout', 'package_lock', 'port_in_use']);
|
|
142
|
+
const RETRYABLE_CLASSES = new Set(['network', 'timeout', 'package_lock', 'resource_locked', 'port_in_use']);
|
|
143
143
|
|
|
144
144
|
function classifyError(msg) {
|
|
145
145
|
const s = String(msg || '').toLowerCase();
|
|
@@ -147,8 +147,15 @@ function classifyError(msg) {
|
|
|
147
147
|
if (/\bbloquead|\brecus|read-only|só-leitura|loop detectad|\bloop:|aprova(ção|r) neg|negou.*aprova/i.test(s)) return 'blocked';
|
|
148
148
|
if (/eacces|permission denied|access is denied|operation not permitted|acesso negado|permissão negada/i.test(s)) return 'permission_denied';
|
|
149
149
|
if (/could not get lock|dpkg was interrupted|resource temporarily unavailable.*lock|another (app|process) .*lock|unable to (acquire|lock)/i.test(s)) return 'package_lock';
|
|
150
|
+
if (/arquivo[_ ]ocupado|file[_ ]locked|resource[_ ]locked|another terminal smart process.*(?:editing|altering)|eexist.*\.lock/i.test(s)) return 'resource_locked';
|
|
150
151
|
if (/eaddrinuse|address already in use|porta .*(ocupad|em uso)|already running/i.test(s)) return 'port_in_use';
|
|
151
|
-
|
|
152
|
+
// Binário/comando inválido pede dialeto/sintaxe correto; módulo ausente pode justificar instalar pacote.
|
|
153
|
+
if (/cannot find module|modulenotfounderror|no module named|is not installed/i.test(s)) return 'missing_dep';
|
|
154
|
+
// cmd.exe pode devolver texto OEM/UTF-8 corrompido ("não" vira "n�o").
|
|
155
|
+
// A classe precisa sobreviver a isso: o recovery correto depende dela e não
|
|
156
|
+
// pode cair em unknown só por causa do encoding do terminal.
|
|
157
|
+
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';
|
|
152
159
|
if (/enoent|no such file|não encontrad|not found|arquivo inexistente|404/i.test(s)) return 'not_found';
|
|
153
160
|
if (/etimedout|timed out|timeout|excedeu o tempo/i.test(s)) return 'timeout';
|
|
154
161
|
if (/econnrefused|enotfound|getaddrinfo|network|connection refused|conexão recusada|conn|dns/i.test(s)) return 'network';
|
package/lib/eval.js
CHANGED
|
@@ -152,7 +152,7 @@ async function judge({ key, baseUrl, model, caseObj, evidence }) {
|
|
|
152
152
|
// opts: { token, lang, model (executor), judgeModel, onCase({i,total,id,phase}) }
|
|
153
153
|
// Roda os casos EM SÉRIE (sandbox isolado por caso; evita colisão e rate-limit).
|
|
154
154
|
async function runSuite(suite, opts = {}) {
|
|
155
|
-
const { token, lang = 'pt', model = null, judgeModel = DEFAULT_JUDGE } = opts;
|
|
155
|
+
const { token, lang = 'pt', model = null, judgeModel = DEFAULT_JUDGE, maxIter, maxDurationMs, maxCredits } = opts;
|
|
156
156
|
const onCase = opts.onCase || (() => {});
|
|
157
157
|
// chave sk-hub (mesma do agente) — reusada pra chamar o juiz direto no gateway.
|
|
158
158
|
const k = await keyring.resolve(token, { feature: 'cli_agent' });
|
|
@@ -188,7 +188,7 @@ async function runSuite(suite, opts = {}) {
|
|
|
188
188
|
out = await agent.run(c.task, {
|
|
189
189
|
token, lang, yes: true, model,
|
|
190
190
|
cwd: sandbox, confineDir: sandbox, readOnly: c.readonly,
|
|
191
|
-
skipSessionStart: true,
|
|
191
|
+
skipSessionStart: true, maxIter, maxDurationMs, maxCredits,
|
|
192
192
|
});
|
|
193
193
|
} catch (e) {
|
|
194
194
|
if (e && (e.code === 'no_credits' || e.status === 402)) throw e; // teto → aborta a suíte
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Autoevolução é deliberadamente separada da execução: incidentes geram uma
|
|
4
|
+
// PROPOSTA revisável, nunca um patch, comando ou deploy automático. Por decisão
|
|
5
|
+
// de produto, somente o GPT SOL pode assinar esse diagnóstico de harness.
|
|
6
|
+
const DIAGNOSTICIAN_MODEL = 'gpt-5.6-sol';
|
|
7
|
+
const telemetry = require('./evolution-telemetry');
|
|
8
|
+
|
|
9
|
+
function diagnosticianFor() { return DIAGNOSTICIAN_MODEL; }
|
|
10
|
+
|
|
11
|
+
function buildRequest(events = [], context = {}) {
|
|
12
|
+
const cleanEvents = telemetry.buildEvents(events, [], context).map(event => ({
|
|
13
|
+
tool: event.tool, errorClass: event.errorClass, pattern: event.pattern,
|
|
14
|
+
stage: event.stage, fingerprint: event.fingerprint, retryable: event.retryable,
|
|
15
|
+
recovered: event.recovered,
|
|
16
|
+
}));
|
|
17
|
+
return {
|
|
18
|
+
model: DIAGNOSTICIAN_MODEL,
|
|
19
|
+
mode: 'proposal_only',
|
|
20
|
+
instruction: 'Analise somente os incidentes sanitizados. Proponha uma hipótese, testes de regressão e uma mudança mínima para revisão humana. Não gere comandos de produção, não aplique patches, não faça deploy e não trate a proposta como correção aprovada.',
|
|
21
|
+
incidents: cleanEvents.slice(0, 20),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeProposal(value, model) {
|
|
26
|
+
const v = value && typeof value === 'object' ? value : {};
|
|
27
|
+
const bySol = String(model || '') === DIAGNOSTICIAN_MODEL;
|
|
28
|
+
const list = (items, limit, width) => (Array.isArray(items) ? items : [])
|
|
29
|
+
.map(item => String(item || '').replace(/\s+/g, ' ').trim().slice(0, width))
|
|
30
|
+
.filter(Boolean).slice(0, limit);
|
|
31
|
+
return {
|
|
32
|
+
accepted: bySol,
|
|
33
|
+
model: bySol ? DIAGNOSTICIAN_MODEL : '',
|
|
34
|
+
mode: 'proposal_only',
|
|
35
|
+
requiresHumanReview: true,
|
|
36
|
+
autoApply: false,
|
|
37
|
+
title: String(v.title || '').replace(/\s+/g, ' ').trim().slice(0, 160),
|
|
38
|
+
diagnosis: String(v.diagnosis || v.summary || '').replace(/\s+/g, ' ').trim().slice(0, 1200),
|
|
39
|
+
evidence: list(v.evidence, 8, 360),
|
|
40
|
+
proposedChanges: list(v.proposedChanges || v.recommendations, 8, 500),
|
|
41
|
+
regressionTests: list(v.regressionTests || v.tests, 8, 360),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function canAutoApply() { return false; }
|
|
46
|
+
|
|
47
|
+
module.exports = { DIAGNOSTICIAN_MODEL, diagnosticianFor, buildRequest, normalizeProposal, canAutoApply };
|
package/lib/file-lock.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Lock cooperativo entre processos do Terminal Smart. `wx` cria o arquivo de
|
|
2
|
+
// forma atômica: duas CLIs não conseguem adquirir o mesmo alvo ao mesmo tempo.
|
|
3
|
+
'use strict';
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
const DIR = path.join(os.homedir(), '.ts', 'locks');
|
|
10
|
+
const STALE_MS = 2 * 60 * 1000;
|
|
11
|
+
const keyFor = (target) => crypto.createHash('sha256').update(path.resolve(target).toLowerCase()).digest('hex').slice(0, 24);
|
|
12
|
+
const pathFor = (target) => path.join(DIR, keyFor(target) + '.lock');
|
|
13
|
+
|
|
14
|
+
function acquire(target, { staleMs = STALE_MS } = {}) {
|
|
15
|
+
const lockPath = pathFor(target);
|
|
16
|
+
try { fs.mkdirSync(DIR, { recursive: true }); } catch (_) { return null; }
|
|
17
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
const fd = fs.openSync(lockPath, 'wx', 0o600);
|
|
20
|
+
try { fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, target: path.resolve(target), createdAt: new Date().toISOString() })); } finally { fs.closeSync(fd); }
|
|
21
|
+
let released = false;
|
|
22
|
+
return {
|
|
23
|
+
release() {
|
|
24
|
+
if (released) return;
|
|
25
|
+
released = true;
|
|
26
|
+
try { fs.unlinkSync(lockPath); } catch (_) {}
|
|
27
|
+
},
|
|
28
|
+
lockPath,
|
|
29
|
+
};
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (!err || err.code !== 'EEXIST') return null;
|
|
32
|
+
try {
|
|
33
|
+
const age = Date.now() - fs.statSync(lockPath).mtimeMs;
|
|
34
|
+
if (age > staleMs) { fs.unlinkSync(lockPath); continue; }
|
|
35
|
+
} catch (_) { continue; }
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { DIR, STALE_MS, keyFor, pathFor, acquire };
|
package/lib/gateways.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Gateways operacionais: aliases OpenSSH com comando forçado (não são shells
|
|
2
|
+
// remotos normais). A configuração guarda apenas alias e allowlist; chaves e
|
|
3
|
+
// credenciais continuam exclusivamente no OpenSSH do sistema.
|
|
4
|
+
const config = require('./config');
|
|
5
|
+
|
|
6
|
+
const OPS = Object.freeze(['status', 'logs', 'deploy']);
|
|
7
|
+
|
|
8
|
+
function normalized(value) {
|
|
9
|
+
return String(value || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function safeName(value) {
|
|
13
|
+
const raw = String(value || '').trim();
|
|
14
|
+
if (!raw || /[\\/]/.test(raw) || raw === '.' || raw === '..' || raw.includes('..')) return null;
|
|
15
|
+
const name = normalized(raw).replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
16
|
+
return /^[a-z0-9][a-z0-9_-]{0,63}$/.test(name) ? name : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function safeAlias(value) {
|
|
20
|
+
const alias = String(value || '').trim();
|
|
21
|
+
return /^[A-Za-z0-9_.-]{1,128}$/.test(alias) ? alias : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeOps(value) {
|
|
25
|
+
const values = Array.isArray(value) ? value : String(value || '').split(',');
|
|
26
|
+
const ops = [...new Set(values.map(x => String(x).trim().toLowerCase()).filter(x => OPS.includes(x)))];
|
|
27
|
+
return ops.length ? ops : ['status'];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function list() {
|
|
31
|
+
const raw = config.load().gateways || {};
|
|
32
|
+
return Object.entries(raw).map(([name, item]) => {
|
|
33
|
+
const alias = safeAlias(item && item.sshAlias);
|
|
34
|
+
if (!alias) return null;
|
|
35
|
+
return { name, label: String(item.label || name), sshAlias: alias, operations: normalizeOps(item.operations) };
|
|
36
|
+
}).filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function save(name, input = {}) {
|
|
40
|
+
const id = safeName(name);
|
|
41
|
+
const alias = safeAlias(input.sshAlias);
|
|
42
|
+
if (!id) throw new Error('Nome do gateway inválido. Use letras, números, hífen ou sublinhado.');
|
|
43
|
+
if (!alias) throw new Error('Alias OpenSSH inválido. Use apenas letras, números, ponto, hífen ou sublinhado.');
|
|
44
|
+
const cfg = config.load();
|
|
45
|
+
const gateways = { ...(cfg.gateways || {}) };
|
|
46
|
+
gateways[id] = { label: String(input.label || id).trim().slice(0, 80), sshAlias: alias, operations: normalizeOps(input.operations) };
|
|
47
|
+
config.save({ gateways });
|
|
48
|
+
return { name: id, ...gateways[id] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function remove(name) {
|
|
52
|
+
const id = safeName(name);
|
|
53
|
+
const cfg = config.load();
|
|
54
|
+
const gateways = { ...(cfg.gateways || {}) };
|
|
55
|
+
if (!id || !gateways[id]) return false;
|
|
56
|
+
delete gateways[id];
|
|
57
|
+
config.save({ gateways });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findInText(text, entries = list()) {
|
|
62
|
+
const normalizedText = normalized(text);
|
|
63
|
+
return entries.find(entry => {
|
|
64
|
+
const terms = [entry.name, entry.label, entry.sshAlias].map(normalized).filter(Boolean);
|
|
65
|
+
return terms.some(term => normalizedText.includes(term));
|
|
66
|
+
}) || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { OPS, normalized, safeName, safeAlias, normalizeOps, list, save, remove, findInText };
|
package/lib/intelligence-core.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
|
|
11
|
-
const INTELLIGENCE_CONTRACT =
|
|
11
|
+
const INTELLIGENCE_CONTRACT = 2;
|
|
12
12
|
// Keep this machine-readable. Human/provider notes belong in commit history;
|
|
13
13
|
// catalogFreshness appends an ISO time before parsing this value.
|
|
14
14
|
const PRICE_REVISION = '2026-08-11';
|
|
@@ -53,11 +53,6 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
53
53
|
provider: 'moonshot', contextWindow: 262144, price: { input: 0.75, output: 3.50, cachedInput: 0.15 },
|
|
54
54
|
capabilities: ['tools', 'code', 'vision', 'long-context'], toolReliability: 0.86, quality: 0.84,
|
|
55
55
|
},
|
|
56
|
-
'kimi-k3': {
|
|
57
|
-
upstreamId: 'moonshotai/kimi-k3',
|
|
58
|
-
provider: 'moonshot', contextWindow: 262144, price: { input: 3.00, output: 15.00, cachedInput: 0.30 },
|
|
59
|
-
capabilities: ['tools', 'code', 'planning', 'long-context'], toolReliability: 0.88, quality: 0.91,
|
|
60
|
-
},
|
|
61
56
|
'glm-4.6': {
|
|
62
57
|
upstreamId: 'z-ai/glm-4.6',
|
|
63
58
|
provider: 'zai', contextWindow: 204800, price: { input: 0.50, output: 2.00, cachedInput: 0.10 },
|
|
@@ -109,7 +104,9 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
109
104
|
capabilities: ['tools', 'code', 'planning', 'review'], toolReliability: 0.96, quality: 0.95,
|
|
110
105
|
},
|
|
111
106
|
'claude-haiku-4-5': {
|
|
112
|
-
|
|
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
|
+
upstreamId: 'anthropic/claude-4.5-haiku-20251001',
|
|
113
110
|
provider: 'anthropic', contextWindow: 200000, price: { input: 1.00, output: 5.00, cachedInput: 0.10 },
|
|
114
111
|
capabilities: ['tools', 'code', 'planning', 'execution', 'review', 'vision'], toolReliability: 0.94, quality: 0.88,
|
|
115
112
|
},
|
|
@@ -176,16 +173,24 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
176
173
|
const MODEL_ALIASES = Object.freeze({
|
|
177
174
|
deepseek: 'deepseek-v4-flash',
|
|
178
175
|
'deepseek-v4': 'deepseek-v4-flash',
|
|
179
|
-
kimi: 'kimi-k2.7-code-highspeed',
|
|
180
176
|
glm: 'glm-4.6',
|
|
181
177
|
mimo: 'mimo-v2.5',
|
|
182
178
|
'gemini-lite': 'gemini-2.5-flash-lite',
|
|
183
179
|
'claude-haiku': 'claude-haiku-4-5',
|
|
184
180
|
});
|
|
185
181
|
|
|
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.
|
|
184
|
+
const OFFICIAL_AUTOMATIC_TEAM = Object.freeze({
|
|
185
|
+
planner: Object.freeze({ model: 'gpt-5.6-luna', label: 'Luna', responsibility: 'planejamento' }),
|
|
186
|
+
executor: Object.freeze({ model: 'deepseek-v4-pro', label: 'DeepSeek Pro', responsibility: 'execução' }),
|
|
187
|
+
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' }),
|
|
189
|
+
verifier: Object.freeze({ model: null, label: 'Verificadores determinísticos', responsibility: 'prova objetiva de entrega' }),
|
|
190
|
+
});
|
|
191
|
+
|
|
186
192
|
// Contrato dos agentes funcionais. Isto não é fine-tuning: cada papel recebe um
|
|
187
|
-
// prompt curto
|
|
188
|
-
// ferramentas. O primeiro modelo permitido é o padrão; os seguintes são fallback.
|
|
193
|
+
// prompt curto e continua sujeito à prova das ferramentas.
|
|
189
194
|
const AGENT_ROLES = Object.freeze({
|
|
190
195
|
classifier: Object.freeze({
|
|
191
196
|
prompt: 'Classifique a intenção e devolva somente a estrutura pedida. Não execute ferramentas nem invente dados.',
|
|
@@ -193,35 +198,35 @@ const AGENT_ROLES = Object.freeze({
|
|
|
193
198
|
}),
|
|
194
199
|
planner: Object.freeze({
|
|
195
200
|
prompt: 'Decomponha o objetivo em etapas executáveis, dependências, riscos, critérios de aceite e provas. Não execute a tarefa.',
|
|
196
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['
|
|
201
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['gpt-5.6-luna'], pro: ['gpt-5.6-luna'] }),
|
|
197
202
|
}),
|
|
198
203
|
executor: Object.freeze({
|
|
199
204
|
prompt: 'Execute somente a etapa recebida com as ferramentas autorizadas. Não declare sucesso sem resultado verificável.',
|
|
200
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-
|
|
205
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro'] }),
|
|
201
206
|
}),
|
|
202
207
|
inspector: Object.freeze({
|
|
203
208
|
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.',
|
|
204
|
-
models: Object.freeze({ free: ['gemini-2.5-flash-lite'], basic: ['
|
|
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'] }),
|
|
205
210
|
}),
|
|
206
211
|
corrector: Object.freeze({
|
|
207
212
|
prompt: 'Corrija apenas falhas confirmadas pelo inspetor, preserve o que funciona e repita as provas afetadas. Não amplie o escopo.',
|
|
208
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro'
|
|
213
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro'] }),
|
|
209
214
|
}),
|
|
210
215
|
vision: Object.freeze({
|
|
211
216
|
prompt: 'Extraia fatos observáveis da imagem e separe leitura, inferência e incerteza. Não invente texto ilegível.',
|
|
212
|
-
models: Object.freeze({ free: ['
|
|
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'] }),
|
|
213
218
|
}),
|
|
214
219
|
document: Object.freeze({
|
|
215
220
|
prompt: 'Produza conteúdo estruturado e use o motor nativo do formato solicitado. Preserve editabilidade e valide o arquivo gerado.',
|
|
216
|
-
models: Object.freeze({ free: ['
|
|
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'] }),
|
|
217
222
|
}),
|
|
218
223
|
site_builder: Object.freeze({
|
|
219
224
|
prompt: 'Crie ou edite o site completo, responsivo e acessível. Preserve o existente quando editar e valide a URL publicada.',
|
|
220
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-
|
|
225
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
221
226
|
}),
|
|
222
227
|
cloud_box: Object.freeze({
|
|
223
228
|
prompt: 'Opere a box com mudanças mínimas, registre comandos e valide serviço, porta e URL. Nunca exponha segredos.',
|
|
224
|
-
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-
|
|
229
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
225
230
|
}),
|
|
226
231
|
});
|
|
227
232
|
|
|
@@ -338,6 +343,74 @@ function _cleanText(value, max) {
|
|
|
338
343
|
.slice(0, max);
|
|
339
344
|
}
|
|
340
345
|
|
|
346
|
+
// Dados que cruzam a fronteira da máquina (handoff, painel e telemetria) não
|
|
347
|
+
// precisam de e-mails, URLs, caminhos locais ou comandos literais. Mantemos o
|
|
348
|
+
// sinal operacional, mas removemos detalhes que pertencem somente à sessão local.
|
|
349
|
+
function cleanOperationalText(value, max) {
|
|
350
|
+
return _cleanText(value, max || 300)
|
|
351
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '<email>')
|
|
352
|
+
.replace(/https?:\/\/[^\s"']+/gi, '<url>')
|
|
353
|
+
.replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '<ip>')
|
|
354
|
+
.replace(/[A-Za-z]:\\[^\s"']+/g, '<path>')
|
|
355
|
+
.replace(/\/(?:home|root|opt|var|tmp|Users|mnt)\/[^\s"']+/g, '<path>')
|
|
356
|
+
.replace(/\b(?:nvapi-|AIza)[A-Za-z0-9._-]{12,}\b/gi, '<secret>')
|
|
357
|
+
.replace(/\b[0-9a-f]{32,}\b/gi, '<id>')
|
|
358
|
+
.replace(/\s+/g, ' ').trim().slice(0, max || 300);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function _safeToolName(value) {
|
|
362
|
+
const name = String(value || 'unknown').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80);
|
|
363
|
+
return name || 'unknown';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function _safeModelId(value) {
|
|
367
|
+
const normalized = normalizeModelId(value);
|
|
368
|
+
return MODEL_CATALOG[normalized] || normalized === 'smart' ? normalized : _safeToolName(value);
|
|
369
|
+
}
|
|
370
|
+
|
|
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
|
+
function buildOperationalSummary(input) {
|
|
375
|
+
const x = input || {};
|
|
376
|
+
const usage = x.tokens || x.usage || {};
|
|
377
|
+
const failures = new Map();
|
|
378
|
+
for (const item of (Array.isArray(x.toolErrors) ? x.toolErrors : []).slice(-50)) {
|
|
379
|
+
const tool = _safeToolName(item && item.tool);
|
|
380
|
+
const errorClass = _safeToolName(item && (item.errorClass || item.class || 'unknown'));
|
|
381
|
+
const key = tool + ':' + errorClass;
|
|
382
|
+
const row = failures.get(key) || { tool, errorClass, count: 0, retryable: false };
|
|
383
|
+
row.count++;
|
|
384
|
+
row.retryable = row.retryable || item.retryable === true;
|
|
385
|
+
failures.set(key, row);
|
|
386
|
+
}
|
|
387
|
+
const actions = {};
|
|
388
|
+
for (const action of (Array.isArray(x.actions) ? x.actions : []).slice(0, 200)) {
|
|
389
|
+
const name = _safeToolName(action && action.name);
|
|
390
|
+
actions[name] = (actions[name] || 0) + 1;
|
|
391
|
+
}
|
|
392
|
+
const verification = x.verification || {};
|
|
393
|
+
return {
|
|
394
|
+
version: 1,
|
|
395
|
+
surface: _safeToolName(x.surface || 'cli'),
|
|
396
|
+
model: _safeModelId(x.model || 'smart'),
|
|
397
|
+
status: x.completion && x.completion.ok ? 'completed' : (x.guard ? 'stopped_by_guard' : 'blocked'),
|
|
398
|
+
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),
|
|
409
|
+
verification: { total: Math.max(0, Number(verification.total) || 0), passed: Math.max(0, Number(verification.passed) || 0), allOk: verification.allOk === true },
|
|
410
|
+
cache: Object.assign({ hits: 0, misses: 0, entries: 0 }, x.missionCache || {}),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
341
414
|
function createMemoryRecord(input) {
|
|
342
415
|
const x = input || {};
|
|
343
416
|
const type = MEMORY_TYPES.includes(x.type) ? x.type : 'episodic';
|
|
@@ -385,16 +458,16 @@ function createHandoff(input) {
|
|
|
385
458
|
return {
|
|
386
459
|
version: 1,
|
|
387
460
|
workstreamId: _cleanText(x.workstreamId || 'default', 160),
|
|
388
|
-
goal:
|
|
461
|
+
goal: cleanOperationalText(x.goal, 600),
|
|
389
462
|
status: ['running', 'blocked', 'done', 'paused'].includes(x.status) ? x.status : 'running',
|
|
390
|
-
decisions: (x.decisions || []).slice(0, 20).map(v =>
|
|
463
|
+
decisions: (x.decisions || []).slice(0, 20).map(v => cleanOperationalText(v, 400)),
|
|
391
464
|
changedFiles: (x.changedFiles || []).slice(0, 100).map(v => typeof v === 'string'
|
|
392
|
-
? { path:
|
|
393
|
-
: { path:
|
|
394
|
-
evidence: (x.evidence || []).slice(0, 30).map(v =>
|
|
395
|
-
failedApproaches: (x.failedApproaches || []).slice(0, 20).map(v =>
|
|
396
|
-
openQuestions: (x.openQuestions || []).slice(0, 20).map(v =>
|
|
397
|
-
nextActions: (x.nextActions || []).slice(0, 20).map(v =>
|
|
465
|
+
? { path: cleanOperationalText(v, 180), hash: '' }
|
|
466
|
+
: { path: cleanOperationalText(v.path, 180), hash: _cleanText(v.hash, 128) }),
|
|
467
|
+
evidence: (x.evidence || []).slice(0, 30).map(v => cleanOperationalText(v, 300)),
|
|
468
|
+
failedApproaches: (x.failedApproaches || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
469
|
+
openQuestions: (x.openQuestions || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
470
|
+
nextActions: (x.nextActions || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
398
471
|
budget: Object.assign({ currency: 'USD', spent: 0, remaining: null }, x.budget || {}),
|
|
399
472
|
createdAt: x.createdAt || new Date().toISOString(),
|
|
400
473
|
};
|
|
@@ -411,8 +484,14 @@ function agentRoleContract(role, plan, options) {
|
|
|
411
484
|
const id = Object.prototype.hasOwnProperty.call(AGENT_ROLES, role) ? role : 'executor';
|
|
412
485
|
const planId = normalizeAgentPlan(plan);
|
|
413
486
|
const spec = AGENT_ROLES[id];
|
|
414
|
-
|
|
415
|
-
|
|
487
|
+
// Tarefas complexas nunca usam modelos Flash nos papeis que decidem ou
|
|
488
|
+
// alteram o projeto. Isto vale inclusive no Free: o limite desse plano e de
|
|
489
|
+
// tentativas/orcamento, nao uma degradacao silenciosa da qualidade do papel.
|
|
490
|
+
const complexCore = options && options.complex === true && ['planner', 'executor', 'corrector'].includes(id)
|
|
491
|
+
? (id === 'planner' ? ['gpt-5.6-luna'] : ['deepseek-v4-pro'])
|
|
492
|
+
: null;
|
|
493
|
+
const requested = complexCore || spec.models[planId] || spec.models.free;
|
|
494
|
+
const allowedRaw = complexCore && planId === 'free' ? null : (options && Array.isArray(options.allowedModels) ? options.allowedModels : null);
|
|
416
495
|
const allowAll = !allowedRaw || allowedRaw.includes('*');
|
|
417
496
|
const allowed = allowAll ? null : new Set(allowedRaw.map(normalizeModelId));
|
|
418
497
|
const candidates = requested.map(normalizeModelId).filter(model => model === 'smart' || MODEL_CATALOG[model])
|
|
@@ -436,23 +515,26 @@ function routeModel(task, reliability) {
|
|
|
436
515
|
if (t.kind === 'vision') required.add('vision');
|
|
437
516
|
const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(normalizeModelId).filter(id => MODEL_CATALOG[id] && id !== 'smart');
|
|
438
517
|
const rel = reliability || {};
|
|
518
|
+
const plan = String(t.plan || '').toLowerCase();
|
|
519
|
+
const isPaid = plan && plan !== 'free' && plan !== 'gratuito';
|
|
520
|
+
|
|
439
521
|
const scored = candidates.map(id => {
|
|
440
522
|
const m = MODEL_CATALOG[id];
|
|
441
523
|
const missing = [...required].filter(c => !m.capabilities.includes(c)).length;
|
|
442
524
|
const observed = rel[id] && Number.isFinite(rel[id].successRate) ? rel[id].successRate : m.toolReliability;
|
|
443
525
|
const price = m.price.input + m.price.output;
|
|
444
|
-
const qualityWeight = t.risk === 'high' ? 0.55 : 0.35;
|
|
445
|
-
const costWeight = t.risk === 'high' ? 0.10 : 0.30;
|
|
526
|
+
const qualityWeight = isPaid ? 0.75 : (t.risk === 'high' ? 0.55 : 0.35);
|
|
527
|
+
const costWeight = isPaid ? 0.05 : (t.risk === 'high' ? 0.10 : 0.30);
|
|
446
528
|
const score = (observed * 0.35) + (m.quality * qualityWeight) + ((1 / (1 + price)) * costWeight) - (missing * 2);
|
|
447
529
|
return { id, score: +score.toFixed(6), missing };
|
|
448
530
|
}).sort((a, b) => b.score - a.score);
|
|
449
|
-
return { model: scored[0] ? scored[0].id : 'deepseek-v4-flash', ranked: scored };
|
|
531
|
+
return { model: scored[0] ? scored[0].id : (isPaid ? 'deepseek-v4-pro' : 'deepseek-v4-flash'), ranked: scored };
|
|
450
532
|
}
|
|
451
533
|
|
|
452
534
|
module.exports = {
|
|
453
535
|
INTELLIGENCE_CONTRACT, PRICE_REVISION, MODEL_CATALOG, MODEL_ALIASES, DEFAULT_PRICE,
|
|
454
|
-
MEMORY_TYPES, MEMORY_TRUST, CONTEXT_SOURCES, AGENT_ROLES,
|
|
536
|
+
MEMORY_TYPES, MEMORY_TRUST, CONTEXT_SOURCES, OFFICIAL_AUTOMATIC_TEAM, AGENT_ROLES,
|
|
455
537
|
normalizeModelId, modelInfo, legacyPriceMap, normalizeUsage, estimateCostUsd, catalogFreshness,
|
|
456
|
-
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff,
|
|
538
|
+
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff, cleanOperationalText, buildOperationalSummary,
|
|
457
539
|
normalizeAgentPlan, agentRoleContract, routeModel,
|
|
458
540
|
};
|
package/lib/meta.js
CHANGED
|
@@ -973,6 +973,10 @@ async function _llmText({ token, model, system, user, maxTokens = 1200, creditBu
|
|
|
973
973
|
// "pensador" numa análise FOCADA (sem ferramentas, sem loop) com o erro + o código
|
|
974
974
|
// dos arquivos citados, e devolve um diagnóstico + fix EXATO pro executor aplicar.
|
|
975
975
|
async function escalate(buildErr, projDir, thinker, token) {
|
|
976
|
+
// Diagnóstico de falha repetida não segue a escolha genérica de "pensador":
|
|
977
|
+
// somente o GPT SOL pode propor causa/fix. O executor continua sendo quem
|
|
978
|
+
// aplica a alteração e os gates continuam exigindo prova posterior.
|
|
979
|
+
const diagnostician = require('./evolution-proposal').diagnosticianFor(thinker);
|
|
976
980
|
// ── DIAGNÓSTICO ANTES DE ESCALAR (v0.52): em vez de só chutar pelo TEXTO do erro, o TS
|
|
977
981
|
// INVESTIGA o sistema real com sondas só-leitura (hipótese→sonda→verificação adversarial)
|
|
978
982
|
// e entrega a CAUSA RAIZ ao pensador. Só na escalação (já estamos travados) → custo contido.
|
|
@@ -980,7 +984,7 @@ async function escalate(buildErr, projDir, thinker, token) {
|
|
|
980
984
|
let diagBlock = '', diagCred = 0;
|
|
981
985
|
if (process.env.TS_META_DIAGNOSE !== '0') {
|
|
982
986
|
try {
|
|
983
|
-
const dr = await require('./diagnose').diagnose(String(buildErr).slice(0, 1400), { token, cwd: projDir, model:
|
|
987
|
+
const dr = await require('./diagnose').diagnose(String(buildErr).slice(0, 1400), { token, cwd: projDir, model: diagnostician, maxRounds: Number(process.env.TS_META_DIAGNOSE_ROUNDS) || 4 });
|
|
984
988
|
diagCred = dr.credits || 0;
|
|
985
989
|
if (dr.status === 'solved' && dr.rootCause) diagBlock = `\n\nDIAGNÓSTICO (o TS investigou o sistema com sondas só-leitura${dr.verified ? ', causa VERIFICADA' : ''}):\nCAUSA RAIZ: ${dr.rootCause}\nFIX apontado: ${dr.fix || '—'}\n(Use isto como base — é evidência do sistema real, não palpite.)`;
|
|
986
990
|
} catch (_) {}
|
|
@@ -991,7 +995,7 @@ async function escalate(buildErr, projDir, thinker, token) {
|
|
|
991
995
|
const sys = 'Você é um engenheiro sênior de DEBUG. Recebe o ERRO REAL de um build, um DIAGNÓSTICO do sistema (se houver) e o código dos arquivos citados. '
|
|
992
996
|
+ 'Se houver DIAGNÓSTICO, PARTA DELE (é evidência do sistema real). Diga a CAUSA-RAIZ em 1-2 linhas e depois o FIX EXATO e mínimo: em QUAL arquivo, QUAL linha/trecho, e o QUE trocar (mostre o antes→depois curto). '
|
|
993
997
|
+ 'NÃO reescreva o app inteiro, só o necessário pra compilar. Seja preciso e direto.';
|
|
994
|
-
const r = await _llmText({ token, model:
|
|
998
|
+
const r = await _llmText({ token, model: diagnostician, system: sys, user: `ERRO DO BUILD:\n${String(buildErr).slice(0, 2500)}${diagBlock}\n\nCÓDIGO RELEVANTE:${ctx.slice(0, 6000)}`, maxTokens: 1200 });
|
|
995
999
|
return { hint: (diagBlock ? diagBlock.trim() + '\n\n' : '') + r.text, credits: (r.credits || 0) + diagCred };
|
|
996
1000
|
}
|
|
997
1001
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const core = require('./core');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Módulo de Auditoria Privada do Dono para sessões de teste e homologação.
|
|
8
|
+
* Opt-in, temporário, estritamente sanitizado.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function sanitizeAuditString(value, allowContent = false) {
|
|
12
|
+
if (typeof value !== 'string') return '';
|
|
13
|
+
let str = core.redactSecrets ? core.redactSecrets(value) : value;
|
|
14
|
+
|
|
15
|
+
// Remoção incondicional de segredos e tokens sensíveis
|
|
16
|
+
str = str
|
|
17
|
+
.replace(/\b(?:Bearer\s+)[A-Za-z0-9._~+/-]+=*/gi, 'Bearer [REDACTED]')
|
|
18
|
+
.replace(/\b(?:ya29\.[A-Za-z0-9_-]+)/gi, '[GOOGLE_OAUTH_TOKEN]')
|
|
19
|
+
.replace(/\b(?:ghp_|gho_|github_pat_)[A-Za-z0-9_]{16,}/gi, '[GITHUB_TOKEN]')
|
|
20
|
+
.replace(/\b(?:nvapi-|AIza|sk-|xox[baprs]-)[A-Za-z0-9._-]{10,}/gi, '[API_KEY]')
|
|
21
|
+
.replace(/-----BEGIN[ A-Z0-9_-]*PRIVATE KEY-----[\s\S]*?-----END[ A-Z0-9_-]*PRIVATE KEY-----/gi, '[PRIVATE_KEY]')
|
|
22
|
+
.replace(/(?:password|passwd|secret|token|authorization|auth_token)\s*[:=]\s*["']?[^"',\s]+["']?/gi, '$1=[REDACTED]');
|
|
23
|
+
|
|
24
|
+
if (!allowContent) {
|
|
25
|
+
// Mascara URLs completas com parâmetros e e-mails se não for incidente com autorização explícita
|
|
26
|
+
str = str
|
|
27
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '<email>')
|
|
28
|
+
.replace(/https?:\/\/[^\s"']+/gi, '<url>');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return str.replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 1000);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isOwnerAuditActive(options = {}, env = process.env) {
|
|
35
|
+
if (options.ownerAudit === true) return true;
|
|
36
|
+
const envVal = String(env.TS_OWNER_AUDIT || '').toLowerCase();
|
|
37
|
+
return envVal === '1' || envVal === 'true' || envVal === 'on';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class OwnerAuditSession {
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.sessionId = options.sessionId || crypto.randomBytes(8).toString('hex');
|
|
43
|
+
this.startedAt = Date.now();
|
|
44
|
+
this.appVersion = options.appVersion || '0.97.29';
|
|
45
|
+
this.surface = options.surface || 'cli';
|
|
46
|
+
this.accountType = 'owner';
|
|
47
|
+
this.explicitContentAuthorization = options.explicitContentAuthorization === true;
|
|
48
|
+
this.records = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
recordStep({ stage, tool, durationMs, result, recovered, costTokens, costUsd, meta = {} }) {
|
|
52
|
+
const sanitizedMeta = {};
|
|
53
|
+
if (meta && typeof meta === 'object') {
|
|
54
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
55
|
+
if (typeof v === 'string') {
|
|
56
|
+
sanitizedMeta[k] = sanitizeAuditString(v, this.explicitContentAuthorization);
|
|
57
|
+
} else if (typeof v === 'number' || typeof v === 'boolean') {
|
|
58
|
+
sanitizedMeta[k] = v;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const entry = {
|
|
64
|
+
id: crypto.randomBytes(6).toString('hex'),
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
stage: String(stage || 'step').slice(0, 50),
|
|
67
|
+
tool: String(tool || 'system').slice(0, 80),
|
|
68
|
+
durationMs: Number(durationMs) || 0,
|
|
69
|
+
result: result === 'success' || result === true ? 'success' : (result === 'blocked' ? 'blocked' : 'failed'),
|
|
70
|
+
recovered: recovered === true,
|
|
71
|
+
version: this.appVersion,
|
|
72
|
+
costTokens: Number(costTokens) || 0,
|
|
73
|
+
costUsd: Number(costUsd) || 0,
|
|
74
|
+
meta: sanitizedMeta
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.records.push(entry);
|
|
78
|
+
if (this.records.length > 500) {
|
|
79
|
+
this.records.shift();
|
|
80
|
+
}
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getSummary() {
|
|
85
|
+
const totalDuration = this.records.reduce((sum, r) => sum + r.durationMs, 0);
|
|
86
|
+
const totalCostTokens = this.records.reduce((sum, r) => sum + r.costTokens, 0);
|
|
87
|
+
const totalCostUsd = this.records.reduce((sum, r) => sum + r.costUsd, 0);
|
|
88
|
+
const successCount = this.records.filter(r => r.result === 'success').length;
|
|
89
|
+
const failedCount = this.records.filter(r => r.result === 'failed').length;
|
|
90
|
+
const recoveredCount = this.records.filter(r => r.recovered).length;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
sessionId: this.sessionId,
|
|
94
|
+
appVersion: this.appVersion,
|
|
95
|
+
surface: this.surface,
|
|
96
|
+
totalSteps: this.records.length,
|
|
97
|
+
successCount,
|
|
98
|
+
failedCount,
|
|
99
|
+
recoveredCount,
|
|
100
|
+
totalDurationMs: totalDuration,
|
|
101
|
+
totalCostTokens,
|
|
102
|
+
totalCostUsd: Number(totalCostUsd.toFixed(6)),
|
|
103
|
+
records: [...this.records]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
isOwnerAuditActive,
|
|
110
|
+
sanitizeAuditString,
|
|
111
|
+
OwnerAuditSession
|
|
112
|
+
};
|
package/lib/providers.js
CHANGED
|
@@ -59,7 +59,10 @@ const PROVEDORES = {
|
|
|
59
59
|
openrouter: {
|
|
60
60
|
nome: 'OpenRouter',
|
|
61
61
|
baseUrl: 'https://openrouter.ai/api/v1',
|
|
62
|
-
|
|
62
|
+
// O padrão precisa refletir o produto: Flash fica restrito ao Free no
|
|
63
|
+
// roteador do TS; para uma conexão OpenRouter sem modelo explícito, use o
|
|
64
|
+
// executor oficial pago em vez do legado deepseek-chat.
|
|
65
|
+
modeloPadrao: 'deepseek/deepseek-v4-pro',
|
|
63
66
|
prefixo: /^sk-or-/,
|
|
64
67
|
gratuito: false,
|
|
65
68
|
limite: 'free tier com rate limit frequente',
|
package/lib/recovery.js
CHANGED
|
@@ -16,10 +16,22 @@ const STRATEGIES = {
|
|
|
16
16
|
diag: 'confirme o nome EXATO do módulo/binário que faltou na mensagem de erro',
|
|
17
17
|
fix: 'instale-o: Node `npm i <pkg>` · Python `pip install <pkg>` · sistema `sudo DEBIAN_FRONTEND=noninteractive apt-get install -y <pkg>` (timeout_s alto). Depois re-tente o comando original.',
|
|
18
18
|
},
|
|
19
|
+
invalid_command: {
|
|
20
|
+
diag: 'leia a mensagem e confirme o shell/sistema atual; no Windows cmd.exe não aceita grep, tail, ls ou cmdlets PowerShell soltos',
|
|
21
|
+
fix: 'não repita a mesma linha. Use a ferramenta de arquivo quando houver uma, ou refaça no dialeto correto: Windows `dir`/`findstr` ou `powershell -NoProfile -Command "..."`; Linux o comando POSIX correspondente.',
|
|
22
|
+
},
|
|
23
|
+
invalid_workdir: {
|
|
24
|
+
diag: 'confirme a pasta atual com info_sistema/listar_diretorio e localize o projeto com buscar_arquivos; não suponha que o caminho existe',
|
|
25
|
+
fix: 'mude para a pasta encontrada com mudar_diretorio e só então execute novamente. Não tente sair da raiz autorizada nem invente caminho absoluto.',
|
|
26
|
+
},
|
|
19
27
|
package_lock: {
|
|
20
28
|
diag: 'veja qual processo segura o lock (apt/dpkg/npm) — NÃO o mate no meio',
|
|
21
29
|
fix: 'espere o processo dono do lock terminar e re-tente. Se o dpkg foi interrompido antes: `sudo dpkg --configure -a`. Nunca apague o lockfile na força.',
|
|
22
30
|
},
|
|
31
|
+
resource_locked: {
|
|
32
|
+
diag: 'outro Terminal Smart está alterando o mesmo arquivo; não leia/escreva uma cópia concorrente',
|
|
33
|
+
fix: 'aguarde alguns segundos e tente a MESMA alteração uma vez. Se continuar ocupado, informe o arquivo e peça para concluir/cancelar a outra missão; nunca force remoção do lock.',
|
|
34
|
+
},
|
|
23
35
|
permission_denied: {
|
|
24
36
|
diag: 'cheque dono e modo do alvo: `ls -l <caminho>` (Linux) / atributos do arquivo',
|
|
25
37
|
fix: 'ajuste o dono/modo do arquivo ESPECÍFICO (`chown user:grp <arquivo>` / `chmod u+w <arquivo>`) ou use sudo só onde apropriado. NUNCA `chmod 777` nem recursivo na raiz.',
|