terminal-smart-cli 0.97.28 → 0.97.30
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 +59 -5
- package/lib/acp.js +40 -18
- package/lib/agent.js +508 -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 +102 -29
- package/lib/meta.js +28 -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
|
@@ -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,7 +173,6 @@ 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',
|
|
@@ -193,35 +189,35 @@ const AGENT_ROLES = Object.freeze({
|
|
|
193
189
|
}),
|
|
194
190
|
planner: Object.freeze({
|
|
195
191
|
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: ['
|
|
192
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['gpt-5.6-luna', 'deepseek-v4-pro'], pro: ['gpt-5.6-luna', 'deepseek-v4-pro', 'glm-5.2'] }),
|
|
197
193
|
}),
|
|
198
194
|
executor: Object.freeze({
|
|
199
195
|
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-
|
|
196
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
201
197
|
}),
|
|
202
198
|
inspector: Object.freeze({
|
|
203
199
|
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: ['
|
|
200
|
+
models: Object.freeze({ free: ['gemini-2.5-flash-lite'], basic: ['gpt-5.6-luna', 'gemini-2.5-flash'], pro: ['gpt-5.6-luna', 'gemini-2.5-flash'] }),
|
|
205
201
|
}),
|
|
206
202
|
corrector: Object.freeze({
|
|
207
203
|
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', '
|
|
204
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna', 'glm-5.2'] }),
|
|
209
205
|
}),
|
|
210
206
|
vision: Object.freeze({
|
|
211
207
|
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: ['
|
|
208
|
+
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
209
|
}),
|
|
214
210
|
document: Object.freeze({
|
|
215
211
|
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: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', '
|
|
212
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', 'mimo-v2.5', 'gemini-2.5-flash'], pro: ['mimo-v2.5', 'deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
217
213
|
}),
|
|
218
214
|
site_builder: Object.freeze({
|
|
219
215
|
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-
|
|
216
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
221
217
|
}),
|
|
222
218
|
cloud_box: Object.freeze({
|
|
223
219
|
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-
|
|
220
|
+
models: Object.freeze({ free: ['deepseek-v4-flash'], basic: ['deepseek-v4-pro'], pro: ['deepseek-v4-pro', 'gpt-5.6-luna'] }),
|
|
225
221
|
}),
|
|
226
222
|
});
|
|
227
223
|
|
|
@@ -338,6 +334,74 @@ function _cleanText(value, max) {
|
|
|
338
334
|
.slice(0, max);
|
|
339
335
|
}
|
|
340
336
|
|
|
337
|
+
// Dados que cruzam a fronteira da máquina (handoff, painel e telemetria) não
|
|
338
|
+
// precisam de e-mails, URLs, caminhos locais ou comandos literais. Mantemos o
|
|
339
|
+
// sinal operacional, mas removemos detalhes que pertencem somente à sessão local.
|
|
340
|
+
function cleanOperationalText(value, max) {
|
|
341
|
+
return _cleanText(value, max || 300)
|
|
342
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '<email>')
|
|
343
|
+
.replace(/https?:\/\/[^\s"']+/gi, '<url>')
|
|
344
|
+
.replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '<ip>')
|
|
345
|
+
.replace(/[A-Za-z]:\\[^\s"']+/g, '<path>')
|
|
346
|
+
.replace(/\/(?:home|root|opt|var|tmp|Users|mnt)\/[^\s"']+/g, '<path>')
|
|
347
|
+
.replace(/\b(?:nvapi-|AIza)[A-Za-z0-9._-]{12,}\b/gi, '<secret>')
|
|
348
|
+
.replace(/\b[0-9a-f]{32,}\b/gi, '<id>')
|
|
349
|
+
.replace(/\s+/g, ' ').trim().slice(0, max || 300);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function _safeToolName(value) {
|
|
353
|
+
const name = String(value || 'unknown').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80);
|
|
354
|
+
return name || 'unknown';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function _safeModelId(value) {
|
|
358
|
+
const normalized = normalizeModelId(value);
|
|
359
|
+
return MODEL_CATALOG[normalized] || normalized === 'smart' ? normalized : _safeToolName(value);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Contrato compacto para painéis e comparação de execuções. É deliberadamente
|
|
363
|
+
// agregado: o painel sabe se a missão funcionou e onde falhou, sem receber o
|
|
364
|
+
// pedido do usuário, conteúdo de e-mail, arquivo, comando ou diretório.
|
|
365
|
+
function buildOperationalSummary(input) {
|
|
366
|
+
const x = input || {};
|
|
367
|
+
const usage = x.tokens || x.usage || {};
|
|
368
|
+
const failures = new Map();
|
|
369
|
+
for (const item of (Array.isArray(x.toolErrors) ? x.toolErrors : []).slice(-50)) {
|
|
370
|
+
const tool = _safeToolName(item && item.tool);
|
|
371
|
+
const errorClass = _safeToolName(item && (item.errorClass || item.class || 'unknown'));
|
|
372
|
+
const key = tool + ':' + errorClass;
|
|
373
|
+
const row = failures.get(key) || { tool, errorClass, count: 0, retryable: false };
|
|
374
|
+
row.count++;
|
|
375
|
+
row.retryable = row.retryable || item.retryable === true;
|
|
376
|
+
failures.set(key, row);
|
|
377
|
+
}
|
|
378
|
+
const actions = {};
|
|
379
|
+
for (const action of (Array.isArray(x.actions) ? x.actions : []).slice(0, 200)) {
|
|
380
|
+
const name = _safeToolName(action && action.name);
|
|
381
|
+
actions[name] = (actions[name] || 0) + 1;
|
|
382
|
+
}
|
|
383
|
+
const verification = x.verification || {};
|
|
384
|
+
return {
|
|
385
|
+
version: 1,
|
|
386
|
+
surface: _safeToolName(x.surface || 'cli'),
|
|
387
|
+
model: _safeModelId(x.model || 'smart'),
|
|
388
|
+
status: x.completion && x.completion.ok ? 'completed' : (x.guard ? 'stopped_by_guard' : 'blocked'),
|
|
389
|
+
completionReason: _safeToolName(x.completion && x.completion.reason || 'unknown'),
|
|
390
|
+
steps: Math.max(0, Number(x.steps) || 0),
|
|
391
|
+
durationMs: Math.max(0, Number(x.durationMs) || 0),
|
|
392
|
+
credits: Math.max(0, Number(x.credits) || 0),
|
|
393
|
+
tokens: {
|
|
394
|
+
input: Math.max(0, Number(usage.inTok || usage.input_tokens) || 0),
|
|
395
|
+
output: Math.max(0, Number(usage.outTok || usage.output_tokens) || 0),
|
|
396
|
+
cached: Math.max(0, Number(usage.cachedTok || usage.cacheTok || usage.cached_tokens) || 0),
|
|
397
|
+
},
|
|
398
|
+
actions,
|
|
399
|
+
failures: [...failures.values()].sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool)).slice(0, 20),
|
|
400
|
+
verification: { total: Math.max(0, Number(verification.total) || 0), passed: Math.max(0, Number(verification.passed) || 0), allOk: verification.allOk === true },
|
|
401
|
+
cache: Object.assign({ hits: 0, misses: 0, entries: 0 }, x.missionCache || {}),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
341
405
|
function createMemoryRecord(input) {
|
|
342
406
|
const x = input || {};
|
|
343
407
|
const type = MEMORY_TYPES.includes(x.type) ? x.type : 'episodic';
|
|
@@ -385,16 +449,16 @@ function createHandoff(input) {
|
|
|
385
449
|
return {
|
|
386
450
|
version: 1,
|
|
387
451
|
workstreamId: _cleanText(x.workstreamId || 'default', 160),
|
|
388
|
-
goal:
|
|
452
|
+
goal: cleanOperationalText(x.goal, 600),
|
|
389
453
|
status: ['running', 'blocked', 'done', 'paused'].includes(x.status) ? x.status : 'running',
|
|
390
|
-
decisions: (x.decisions || []).slice(0, 20).map(v =>
|
|
454
|
+
decisions: (x.decisions || []).slice(0, 20).map(v => cleanOperationalText(v, 400)),
|
|
391
455
|
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 =>
|
|
456
|
+
? { path: cleanOperationalText(v, 180), hash: '' }
|
|
457
|
+
: { path: cleanOperationalText(v.path, 180), hash: _cleanText(v.hash, 128) }),
|
|
458
|
+
evidence: (x.evidence || []).slice(0, 30).map(v => cleanOperationalText(v, 300)),
|
|
459
|
+
failedApproaches: (x.failedApproaches || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
460
|
+
openQuestions: (x.openQuestions || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
461
|
+
nextActions: (x.nextActions || []).slice(0, 20).map(v => cleanOperationalText(v, 300)),
|
|
398
462
|
budget: Object.assign({ currency: 'USD', spent: 0, remaining: null }, x.budget || {}),
|
|
399
463
|
createdAt: x.createdAt || new Date().toISOString(),
|
|
400
464
|
};
|
|
@@ -411,8 +475,14 @@ function agentRoleContract(role, plan, options) {
|
|
|
411
475
|
const id = Object.prototype.hasOwnProperty.call(AGENT_ROLES, role) ? role : 'executor';
|
|
412
476
|
const planId = normalizeAgentPlan(plan);
|
|
413
477
|
const spec = AGENT_ROLES[id];
|
|
414
|
-
|
|
415
|
-
|
|
478
|
+
// Tarefas complexas nunca usam modelos Flash nos papeis que decidem ou
|
|
479
|
+
// alteram o projeto. Isto vale inclusive no Free: o limite desse plano e de
|
|
480
|
+
// tentativas/orcamento, nao uma degradacao silenciosa da qualidade do papel.
|
|
481
|
+
const complexCore = options && options.complex === true && ['planner', 'executor', 'corrector'].includes(id)
|
|
482
|
+
? (id === 'planner' ? ['gpt-5.6-luna', 'deepseek-v4-pro'] : ['deepseek-v4-pro', 'gpt-5.6-luna'])
|
|
483
|
+
: null;
|
|
484
|
+
const requested = complexCore || spec.models[planId] || spec.models.free;
|
|
485
|
+
const allowedRaw = complexCore && planId === 'free' ? null : (options && Array.isArray(options.allowedModels) ? options.allowedModels : null);
|
|
416
486
|
const allowAll = !allowedRaw || allowedRaw.includes('*');
|
|
417
487
|
const allowed = allowAll ? null : new Set(allowedRaw.map(normalizeModelId));
|
|
418
488
|
const candidates = requested.map(normalizeModelId).filter(model => model === 'smart' || MODEL_CATALOG[model])
|
|
@@ -436,23 +506,26 @@ function routeModel(task, reliability) {
|
|
|
436
506
|
if (t.kind === 'vision') required.add('vision');
|
|
437
507
|
const candidates = (t.candidates || Object.keys(MODEL_CATALOG)).map(normalizeModelId).filter(id => MODEL_CATALOG[id] && id !== 'smart');
|
|
438
508
|
const rel = reliability || {};
|
|
509
|
+
const plan = String(t.plan || '').toLowerCase();
|
|
510
|
+
const isPaid = plan && plan !== 'free' && plan !== 'gratuito';
|
|
511
|
+
|
|
439
512
|
const scored = candidates.map(id => {
|
|
440
513
|
const m = MODEL_CATALOG[id];
|
|
441
514
|
const missing = [...required].filter(c => !m.capabilities.includes(c)).length;
|
|
442
515
|
const observed = rel[id] && Number.isFinite(rel[id].successRate) ? rel[id].successRate : m.toolReliability;
|
|
443
516
|
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;
|
|
517
|
+
const qualityWeight = isPaid ? 0.75 : (t.risk === 'high' ? 0.55 : 0.35);
|
|
518
|
+
const costWeight = isPaid ? 0.05 : (t.risk === 'high' ? 0.10 : 0.30);
|
|
446
519
|
const score = (observed * 0.35) + (m.quality * qualityWeight) + ((1 / (1 + price)) * costWeight) - (missing * 2);
|
|
447
520
|
return { id, score: +score.toFixed(6), missing };
|
|
448
521
|
}).sort((a, b) => b.score - a.score);
|
|
449
|
-
return { model: scored[0] ? scored[0].id : 'deepseek-v4-flash', ranked: scored };
|
|
522
|
+
return { model: scored[0] ? scored[0].id : (isPaid ? 'deepseek-v4-pro' : 'deepseek-v4-flash'), ranked: scored };
|
|
450
523
|
}
|
|
451
524
|
|
|
452
525
|
module.exports = {
|
|
453
526
|
INTELLIGENCE_CONTRACT, PRICE_REVISION, MODEL_CATALOG, MODEL_ALIASES, DEFAULT_PRICE,
|
|
454
527
|
MEMORY_TYPES, MEMORY_TRUST, CONTEXT_SOURCES, AGENT_ROLES,
|
|
455
528
|
normalizeModelId, modelInfo, legacyPriceMap, normalizeUsage, estimateCostUsd, catalogFreshness,
|
|
456
|
-
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff,
|
|
529
|
+
createContextLedger, createMemoryRecord, scoreMemoryRecord, createHandoff, cleanOperationalText, buildOperationalSummary,
|
|
457
530
|
normalizeAgentPlan, agentRoleContract, routeModel,
|
|
458
531
|
};
|
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
|
|
|
@@ -1167,6 +1171,16 @@ async function _checkCriteria(st, dir) {
|
|
|
1167
1171
|
}
|
|
1168
1172
|
}
|
|
1169
1173
|
|
|
1174
|
+
// Fast fail-closed gate used after every implementation round. File/content
|
|
1175
|
+
// criteria may belong to later checklist items, but command criteria (tests,
|
|
1176
|
+
// lint, typecheck) describe repository health and must stay green throughout.
|
|
1177
|
+
async function _checkRoundCommandCriteria(st, dir) {
|
|
1178
|
+
const criteria = (st.criteria || []).filter(c => c && c.type === 'command');
|
|
1179
|
+
if (!criteria.length) return { allOk: true, passed: 0, total: 0, results: [] };
|
|
1180
|
+
try { return await require('./verify').runAll(criteria, { cwd: dir }); }
|
|
1181
|
+
catch (e) { return { allOk: false, passed: 0, total: criteria.length, results: [], error: String(e && e.message || e) }; }
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1170
1184
|
async function run(goal, opts = {}) {
|
|
1171
1185
|
const { token, lang = 'pt', yes = false, autoAll = false, budget = 400, maxRounds = 20, dir = process.cwd(), model = null, thinker = null, maxMinutes = 0, designer = null, design = 'auto', runGate = true, eye = null, visualLadder = true, prove = false } = opts;
|
|
1172
1186
|
const onChecklist = opts.onChecklist || (() => {});
|
|
@@ -1693,6 +1707,18 @@ async function run(goal, opts = {}) {
|
|
|
1693
1707
|
return st;
|
|
1694
1708
|
}
|
|
1695
1709
|
|
|
1710
|
+
const roundCommandProof = await _checkRoundCommandCriteria(st, dir);
|
|
1711
|
+
if (!roundCommandProof.allOk) {
|
|
1712
|
+
item.attempts = Math.max(0, (item.attempts || 1) - 1);
|
|
1713
|
+
st.status = 'paused'; st.pause_reason = 'verification_failed';
|
|
1714
|
+
st.rounds.push({ item: item.desc, id: item.id,
|
|
1715
|
+
result: `Prova determinística falhou: ${roundCommandProof.passed}/${roundCommandProof.total} comandos passaram. O item permanece aberto para correção.`,
|
|
1716
|
+
credits: out.credits || 0, steps: out.steps || 0, at: new Date().toISOString() });
|
|
1717
|
+
save(st, dir);
|
|
1718
|
+
onAlert({ type: 'verification', text: `Os testes/comandos obrigatórios falharam (${roundCommandProof.passed}/${roundCommandProof.total}). Não marquei o item como concluído; a próxima janela deve corrigir a regressão.` });
|
|
1719
|
+
return st;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1696
1722
|
// ── BLOQUEIO HUMANO: o agente pediu uma ação externa que só o usuário faz ──
|
|
1697
1723
|
// A missão PAUSA (gasto zero enquanto espera) e chama o usuário. Retoma com "ts meta".
|
|
1698
1724
|
if (out.needHuman) {
|
|
@@ -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',
|