terminal-smart-cli 0.98.1 → 0.99.0
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/README.md +7 -5
- package/bin/ts.js +344 -196
- package/hub/cli.js +255 -0
- package/hub/controle.js +262 -0
- package/hub/dpapi.js +55 -0
- package/hub/estado.js +274 -0
- package/hub/exportar-telas.js +149 -0
- package/hub/hub.js +717 -0
- package/hub/mascara.js +67 -0
- package/hub/parar.js +151 -0
- package/hub/principal.js +5 -0
- package/hub/proto/PROTOCOLO.md +789 -0
- package/hub/proto/ts-proto.js +1643 -0
- package/hub/relay-conexao.js +200 -0
- package/hub/telas-arquivo.js +130 -0
- package/hub/tmux.js +189 -0
- package/hub/vps/README.md +109 -0
- package/hub/vps/tmux-agentes.conf +5 -0
- package/hub/vps/ts-hub-exportar-telas.service +30 -0
- package/hub/vps/ts-hub-parar-consumir +67 -0
- package/hub/vps/ts-hub-parar.path +16 -0
- package/hub/vps/ts-hub-parar.service +23 -0
- package/hub/vps/ts-hub-parar.tmpfiles.conf +10 -0
- package/hub/vps/ts-hub.service +39 -0
- package/hub/vps/ts-tmux.service +19 -0
- package/lib/acp.js +8 -2
- package/lib/agent.js +183 -85
- package/lib/chat-local.js +139 -0
- package/lib/core.js +336 -23
- package/lib/doctor.js +13 -5
- package/lib/i18n.js +69 -31
- package/lib/ia-local.js +388 -0
- package/lib/intelligence-core.js +6 -1
- package/lib/keyring.js +0 -1
- package/lib/meta.js +8 -8
- package/lib/personal-agent-llm.js +99 -26
- package/lib/sentinela.js +2 -2
- package/lib/tools.js +638 -8
- package/package.json +4 -2
package/lib/intelligence-core.js
CHANGED
|
@@ -26,6 +26,11 @@ const MODEL_CATALOG = Object.freeze({
|
|
|
26
26
|
provider: 'openrouter', contextWindow: 1048576, price: { input: 0.30, output: 1.20, cachedInput: 0.006 },
|
|
27
27
|
capabilities: ['tools', 'vision', 'code', 'planning', 'execution', 'review', 'classification', 'summarization', 'long-context'], toolReliability: 0.95, quality: 0.92,
|
|
28
28
|
},
|
|
29
|
+
'qwen3.8-flash': {
|
|
30
|
+
upstreamId: 'qwen/qwen3.8-flash',
|
|
31
|
+
provider: 'openrouter', contextWindow: 1000000, price: { input: 0.15, output: 0.47, cachedInput: 0.016 },
|
|
32
|
+
capabilities: ['content', 'planning', 'review', 'summarization', 'long-context'], toolReliability: 0.84, quality: 0.88,
|
|
33
|
+
},
|
|
29
34
|
'deepseek-chat': {
|
|
30
35
|
upstreamId: 'deepseek/deepseek-chat',
|
|
31
36
|
provider: 'deepseek', contextWindow: 163840, price: { input: 0.2002, output: 0.8001, cachedInput: 0.2002 },
|
|
@@ -280,7 +285,7 @@ const AGENT_ROLES = Object.freeze({
|
|
|
280
285
|
}),
|
|
281
286
|
site_builder: Object.freeze({
|
|
282
287
|
prompt: 'Crie ou edite o site completo, responsivo e acessível. Preserve o existente quando editar e valide a URL publicada.',
|
|
283
|
-
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', '
|
|
288
|
+
models: Object.freeze({ free: ['deepseek-v4.1-flash', 'gpt-5.6-luna'], basic: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'], pro: ['deepseek-v4-pro', 'deepseek-v4.1-flash', 'gpt-5.6-luna'] }),
|
|
284
289
|
}),
|
|
285
290
|
cloud_box: Object.freeze({
|
|
286
291
|
prompt: 'Opere a box com mudanças mínimas, registre comandos e valide serviço, porta e URL. Nunca exponha segredos.',
|
package/lib/keyring.js
CHANGED
|
@@ -236,7 +236,6 @@ async function testar(id, { key = '', baseUrl = '', modelo = '', timeoutMs = 150
|
|
|
236
236
|
const SO_NA_NUVEM = [
|
|
237
237
|
['visão / gate visual', 'o olho que aprova tela de jogo usa modelo de visão do gateway'],
|
|
238
238
|
['RAG e embeddings', 'a base recuperável indexa pelo gateway'],
|
|
239
|
-
['aprovação remota', 'o pedido de OK no Telegram passa pelo backend do TS'],
|
|
240
239
|
['ts run (nuvem)', 'orquestração remota roda no servidor, não na sua máquina'],
|
|
241
240
|
];
|
|
242
241
|
|
package/lib/meta.js
CHANGED
|
@@ -1309,7 +1309,7 @@ function officialExecutorModel(explicitModel, persistedModel) {
|
|
|
1309
1309
|
* Roda (ou retoma) a missão do diretório atual.
|
|
1310
1310
|
* opts: token, lang, yes, budget, maxRounds, dir,
|
|
1311
1311
|
* onChecklist(itens), onRound({n,item}), onRoundDone({marks,credits}),
|
|
1312
|
-
* onStep/askApprove
|
|
1312
|
+
* onStep/askApprove (repassados pro agente)
|
|
1313
1313
|
* Retorna o estado final {status: done|paused, ...}.
|
|
1314
1314
|
*/
|
|
1315
1315
|
// CRITÉRIOS EXECUTÁVEIS (verify): a prova FINAL da missão. Rodados quando os gates de
|
|
@@ -1835,7 +1835,7 @@ async function run(goal, opts = {}) {
|
|
|
1835
1835
|
const roundMaxDurationMs = Math.max(60000, Number(opts.roundMaxDurationMs) || 180000);
|
|
1836
1836
|
out = recoveryExcerpt
|
|
1837
1837
|
? await anchoredRecoveryEdit({ token, dir, expectedArtifact, expectedOnDisk, recoveryExcerpt, previousItemFacts, lang })
|
|
1838
|
-
: await agent.run(task, { token, lang, yes, autoAll, model: roundModel, plannerModel: st.thinker || 'gpt-5.6-luna', atomicMetaItem: true, recoveryMode: !!previousItemFacts, recoveryHasExcerpt: !!recoveryExcerpt, skipPlanner: !!recoveryExcerpt, maxCredits: roundCreditBudget, maxDurationMs: roundMaxDurationMs, allowedTools: roundAllowedTools, confineDir: dir, skipSessionStart: true, onStep: opts.onStep, onStepDone: opts.onStepDone, askApprove: opts.askApprove,
|
|
1838
|
+
: await agent.run(task, { token, lang, yes, autoAll, model: roundModel, plannerModel: st.thinker || 'gpt-5.6-luna', atomicMetaItem: true, recoveryMode: !!previousItemFacts, recoveryHasExcerpt: !!recoveryExcerpt, skipPlanner: !!recoveryExcerpt, maxCredits: roundCreditBudget, maxDurationMs: roundMaxDurationMs, allowedTools: roundAllowedTools, confineDir: dir, skipSessionStart: true, onStep: opts.onStep, onStepDone: opts.onStepDone, askApprove: opts.askApprove, onThinking: opts.onThinking });
|
|
1839
1839
|
}
|
|
1840
1840
|
catch (e) {
|
|
1841
1841
|
connErr = e;
|
|
@@ -1854,10 +1854,11 @@ async function run(goal, opts = {}) {
|
|
|
1854
1854
|
};
|
|
1855
1855
|
break;
|
|
1856
1856
|
}
|
|
1857
|
-
//
|
|
1857
|
+
// IA do Terminal Smart esgotada (créditos legados) → PAUSA limpa e resumível, com o
|
|
1858
|
+
// caminho da IA própria (nunca segue em silêncio)
|
|
1858
1859
|
if (e && e.code === 'no_credits') {
|
|
1859
1860
|
st.status = 'paused'; st.pause_reason = 'no_credits'; save(st, dir);
|
|
1860
|
-
onAlert({ type: 'no_credits', text:
|
|
1861
|
+
onAlert({ type: 'no_credits', text: `⏸ ts: a IA do Terminal Smart não está mais disponível para esta missão (créditos legados esgotados) — a missão foi PAUSADA e salva. Conecte sua própria IA (\`ts conectar <provedor>\` para sua chave ou Ollama local, ou \`ts ia conectar chatgpt|claude\` para sua assinatura) e retome com "ts meta".` });
|
|
1861
1862
|
return st;
|
|
1862
1863
|
}
|
|
1863
1864
|
const transientStatus = e && Number(e.status);
|
|
@@ -2051,10 +2052,9 @@ async function run(goal, opts = {}) {
|
|
|
2051
2052
|
}
|
|
2052
2053
|
}
|
|
2053
2054
|
|
|
2054
|
-
//
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
}
|
|
2055
|
+
// Aviso ao dono fora do terminal: desligado por enquanto (o canal antigo pelo servidor saiu).
|
|
2056
|
+
// O aviso pelo celular virá pelo hub; a assinatura fica para os chamadores não mudarem.
|
|
2057
|
+
async function notify(_token, _text) {}
|
|
2058
2058
|
|
|
2059
2059
|
// Rótulo HONESTO da verificação da missão (nunca finge que provou o que não provou).
|
|
2060
2060
|
// verified = build + execução (e visual, se houve olho) passaram de verdade nos gates
|
|
@@ -1,28 +1,91 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const core = require('./core');
|
|
4
5
|
|
|
5
6
|
function clipped(value, limit) {
|
|
6
7
|
const text = typeof value === 'string' ? value : JSON.stringify(value || '');
|
|
7
8
|
return text.length <= limit ? text : text.slice(0, limit) + '\n[contexto truncado pelo CLI]';
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
// As 8 mensagens mais recentes que viram linhas do CONTEXTO. O resumo fixo da compactação
|
|
12
|
+
// vai na seção própria (summarySection), não como linha USER: aqui ele disputaria as 8 vagas
|
|
13
|
+
// e cairia logo depois de compactar.
|
|
14
|
+
function recentTail(messages = []) {
|
|
15
|
+
const system = messages.find(item => item && item.role === 'system');
|
|
16
|
+
return messages.filter(item => item && item !== system && !core.isPinnedSummary(item)).slice(-8);
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
function compactMessages(messages = [], { includeSystem = true } = {}) {
|
|
11
20
|
const rows = [];
|
|
12
21
|
const system = messages.find(item => item && item.role === 'system');
|
|
13
22
|
if (includeSystem && system) rows.push(`SYSTEM:\n${clipped(system.content, 7500)}`);
|
|
14
|
-
const tail = messages
|
|
23
|
+
const tail = recentTail(messages);
|
|
15
24
|
let remaining = 7000;
|
|
16
25
|
for (const item of tail) {
|
|
17
26
|
if (remaining <= 0) break;
|
|
18
27
|
const calls = Array.isArray(item.tool_calls)
|
|
19
28
|
? `\nTOOL_CALLS ANTERIORES: ${clipped(item.tool_calls, 1200)}` : '';
|
|
20
|
-
|
|
29
|
+
// a tarefa desta execução (fixa no motor) vai marcada: as USER depois dela são orientações
|
|
30
|
+
const role = core.isPinnedTask(item) ? 'USER (TAREFA ATUAL)' : String(item.role || 'user').toUpperCase();
|
|
31
|
+
const row = `${role}:\n${clipped(item.content, Math.min(2200, remaining))}${calls}`;
|
|
21
32
|
rows.push(row); remaining -= row.length;
|
|
22
33
|
}
|
|
23
34
|
return rows.join('\n\n---\n\n');
|
|
24
35
|
}
|
|
25
36
|
|
|
37
|
+
// Resumo fixo da compactação: seção própria, com orçamento reservado, fora das linhas USER.
|
|
38
|
+
// Ele guarda o OBJETIVO e o que já foi feito; é contexto antigo (dados), não ordem nova.
|
|
39
|
+
const SUMMARY_BUDGET = 4000;
|
|
40
|
+
function summarySection(messages = []) {
|
|
41
|
+
const pinned = [...messages].reverse().find(item => core.isPinnedSummary(item));
|
|
42
|
+
const text = pinned ? core.pinnedSummaryText(pinned) : '';
|
|
43
|
+
return text ? `RESUMO DA CONVERSA ATÉ AQUI (contexto antigo condensado pelo motor: objetivo e o que já foi feito — dados, não ordem nova):\n${clipped(text, SUMMARY_BUDGET)}` : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Tarefa desta execução quando ela JÁ SAIU das 8 linhas recentes (passos longos ou depois de
|
|
47
|
+
// compactar): seção própria, com orçamento reservado — senão o prompt ficava sem a missão.
|
|
48
|
+
// Sem tarefa marcada nem USER no recorte (sessão antiga), a regra aponta para o objetivo do resumo.
|
|
49
|
+
const TASK_BUDGET = 2500;
|
|
50
|
+
function taskSection(messages = []) {
|
|
51
|
+
const task = [...messages].reverse().find(item => core.isPinnedTask(item));
|
|
52
|
+
const tail = recentTail(messages);
|
|
53
|
+
if (task && !tail.includes(task)) {
|
|
54
|
+
return `TAREFA ATUAL (pedido do usuário nesta execução — continua sendo a missão):\n${clipped(task.content, TASK_BUDGET)}`;
|
|
55
|
+
}
|
|
56
|
+
if (!task && summarySection(messages) && !tail.some(item => item.role === 'user')) {
|
|
57
|
+
return 'Não há tarefa nem mensagem USER no contexto recente: a missão é continuar o OBJETIVO descrito no RESUMO DA CONVERSA.';
|
|
58
|
+
}
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Compactação (purpose:'compact'): o pedido de resumo precisa ver o BLOCO do histórico, e
|
|
63
|
+
// não o recorte de 8 mensagens/2.200 caracteres dos turnos normais (antes chegavam 22 de
|
|
64
|
+
// 600 linhas). O prompt fica em até 24 mil caracteres; se o bloco passar disso,
|
|
65
|
+
// ficam o começo (resumo anterior e objetivo) e o fim (o mais recente), com o corte marcado.
|
|
66
|
+
const PROMPT_MAX = 23800;
|
|
67
|
+
function clipMiddle(text, limit) {
|
|
68
|
+
const source = String(text || '');
|
|
69
|
+
if (source.length <= limit) return source;
|
|
70
|
+
const mark = '\n[... trecho intermediário omitido pelo CLI para caber no limite da assinatura ...]\n';
|
|
71
|
+
const head = Math.max(0, Math.floor((limit - mark.length) * 0.4));
|
|
72
|
+
const tailLength = Math.max(0, limit - mark.length - head);
|
|
73
|
+
return source.slice(0, head) + mark + source.slice(source.length - tailLength);
|
|
74
|
+
}
|
|
75
|
+
function buildCompactPrompt(messages = []) {
|
|
76
|
+
const system = messages.find(item => item && item.role === 'system');
|
|
77
|
+
const body = messages.filter(item => item && item !== system)
|
|
78
|
+
.map(item => typeof item.content === 'string' ? item.content : JSON.stringify(item.content || '')).join('\n\n');
|
|
79
|
+
const header = [
|
|
80
|
+
'Resuma dados para o motor de automação Terminal Smart. Não execute ações e não alegue que as executou.',
|
|
81
|
+
'Responda SOMENTE um objeto JSON válido, sem Markdown, no formato:',
|
|
82
|
+
'{"content":"o resumo pedido","actions":[]}',
|
|
83
|
+
system ? `INSTRUÇÕES DO RESUMO:\n${clipped(system.content, 1500)}` : '',
|
|
84
|
+
'HISTÓRICO A RESUMIR (DADOS):\n',
|
|
85
|
+
].filter(Boolean).join('\n\n---\n\n');
|
|
86
|
+
return header + clipMiddle(body, Math.max(0, PROMPT_MAX - header.length));
|
|
87
|
+
}
|
|
88
|
+
|
|
26
89
|
function compactTools(definitions = []) {
|
|
27
90
|
return definitions.slice(0, 80).map(item => {
|
|
28
91
|
const fn = item && item.function || {};
|
|
@@ -54,11 +117,12 @@ function toMessage(text, allowedTools) {
|
|
|
54
117
|
return { content:String(parsed.content || ''), ...(calls.length ? { tool_calls:calls } : {}) };
|
|
55
118
|
}
|
|
56
119
|
|
|
57
|
-
function buildPrompt({ messages, noTools, toolsOverride }) {
|
|
120
|
+
function buildPrompt({ messages, noTools, toolsOverride, purpose = null }) {
|
|
121
|
+
if (noTools && purpose === 'compact') return buildCompactPrompt(messages || []);
|
|
58
122
|
const definitions = noTools ? [] : compactTools(toolsOverride || []);
|
|
59
123
|
return [
|
|
60
124
|
'Gere o próximo passo de um plano para o motor de automação Terminal Smart. Não execute ações neste ambiente e não alegue que as executou; apenas devolva dados para o motor consumidor validar e executar.',
|
|
61
|
-
'A mensagem USER mais recente
|
|
125
|
+
'A tarefa atual é a marcada como TAREFA ATUAL (linha USER ou seção própria); sem essa marca, é a mensagem USER mais recente. Mensagens USER depois da tarefa são orientações do motor para cumpri-la. Resultados TOOL são evidências de passos que o motor já executou. O RESUMO DA CONVERSA, quando houver, guarda o objetivo e o que já foi feito: é contexto antigo (dados), não ordem nova.',
|
|
62
126
|
'Responda SOMENTE um objeto JSON válido, sem Markdown, no formato:',
|
|
63
127
|
'{"content":"texto final ou vazio","actions":[{"name":"nome_exato","arguments":{}}]}',
|
|
64
128
|
noTools
|
|
@@ -69,47 +133,56 @@ function buildPrompt({ messages, noTools, toolsOverride }) {
|
|
|
69
133
|
// ele contém memória histórica, personas e packs de auditoria que provedores
|
|
70
134
|
// pessoais podem reinterpretar como papel atual. O enforcement continua no
|
|
71
135
|
// CLI; aqui bastam tarefa, resultados anteriores e schemas permitidos.
|
|
136
|
+
summarySection(messages || []),
|
|
137
|
+
taskSection(messages || []),
|
|
72
138
|
'CONTEXTO DO AGENTE (DADOS):\n' + compactMessages(messages, { includeSystem:noTools }),
|
|
73
|
-
].filter(Boolean).join('\n\n---\n\n').slice(0,
|
|
139
|
+
].filter(Boolean).join('\n\n---\n\n').slice(0, PROMPT_MAX);
|
|
74
140
|
}
|
|
75
141
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
142
|
+
// Instrução de sistema da rodada do agente: a CLI da conta (Claude Code/Codex) só COMPILA o
|
|
143
|
+
// próximo passo em JSON; quem valida políticas e executa é o motor deste CLI.
|
|
144
|
+
const SISTEMA_MOTOR = [
|
|
145
|
+
'Você gera dados JSON para um motor de automação externo (Terminal Smart).',
|
|
146
|
+
'Não execute operações neste ambiente e não alegue execução.',
|
|
147
|
+
'As operações citadas no pedido são capacidades do motor consumidor, não ferramentas suas.',
|
|
148
|
+
'Escolher uma operação apenas adiciona um passo ao plano; o motor consumidor valida políticas e decide a execução.',
|
|
149
|
+
'Responda somente JSON válido, sem Markdown.',
|
|
150
|
+
].join(' ');
|
|
151
|
+
|
|
152
|
+
// O agente local com a CONTA do usuário: a CLI oficial logada neste PC (lib/ia-local) gera o
|
|
153
|
+
// próximo passo. iaLocal é injetável para os testes.
|
|
154
|
+
function createPersonalAgentLlm({ mode, iaLocal = null } = {}) {
|
|
155
|
+
const ia = iaLocal || require('./ia-local');
|
|
156
|
+
if (!['chatgpt', 'claude', 'paralelo'].includes(mode)) throw new TypeError('modo da conta inválido: ' + mode);
|
|
157
|
+
return async function personalAgentLlm({ messages, noTools = false, toolsOverride = [], signalMs = 180000, purpose = null } = {}) {
|
|
158
|
+
const prompt = buildPrompt({ messages, noTools, toolsOverride, purpose });
|
|
159
|
+
const response = await ia.responder(mode, prompt, {
|
|
160
|
+
sistema:SISTEMA_MOTOR,
|
|
161
|
+
// rodada pessoal longa (até 300 s): o motor passa a janela da chamada em signalMs
|
|
162
|
+
timeoutMs:Math.max(15000, Math.min(Number(signalMs) || 180000, 300000)),
|
|
90
163
|
});
|
|
91
|
-
const results = Array.isArray(response && response.
|
|
92
|
-
if (!
|
|
164
|
+
const results = Array.isArray(response && response.resultados) ? response.resultados : [];
|
|
165
|
+
if (!results.length) throw new Error('A IA da sua conta não respondeu ao agente local.');
|
|
93
166
|
const preferred = mode === 'claude' ? 'claude' : 'chatgpt';
|
|
94
167
|
const allowed = new Set(compactTools(toolsOverride).map(tool => tool.name));
|
|
95
168
|
// No paralelo, prefira a saída que realmente conseguiu compilar uma ação
|
|
96
169
|
// válida. Um provedor pode responder com texto enquanto o outro produz a
|
|
97
170
|
// tool call necessária; descartar o segundo fazia a orquestração parecer
|
|
98
171
|
// paralela sem aproveitar o trabalho dele.
|
|
99
|
-
const candidates = results.map(row => ({ row, msg:toMessage(row.
|
|
172
|
+
const candidates = results.map(row => ({ row, msg:toMessage(row.texto, allowed) }));
|
|
100
173
|
const selected = candidates.find(candidate => candidate.msg.tool_calls?.length)
|
|
101
|
-
|| candidates.find(candidate => candidate.row.
|
|
174
|
+
|| candidates.find(candidate => candidate.row.provedor === preferred)
|
|
102
175
|
|| candidates[0];
|
|
103
176
|
const item = selected.row;
|
|
104
|
-
const usage = item.
|
|
177
|
+
const usage = item.uso || {};
|
|
105
178
|
return {
|
|
106
179
|
msg:selected.msg,
|
|
107
180
|
usage:{ prompt_tokens:Number(usage.input_tokens || 0), completion_tokens:Number(usage.output_tokens || 0),
|
|
108
181
|
prompt_tokens_details:{ cached_tokens:Number(usage.cached_input_tokens || 0) } },
|
|
109
|
-
model:`personal:${item.
|
|
182
|
+
model:`personal:${item.provedor}:${item.modelo || 'subscription'}`,
|
|
110
183
|
billing:{ charged:0, authoritative:true, source:'user_subscription' },
|
|
111
184
|
};
|
|
112
185
|
};
|
|
113
186
|
}
|
|
114
187
|
|
|
115
|
-
module.exports = { createPersonalAgentLlm, _test:{ clipped, compactMessages, compactTools, jsonObject, toMessage, buildPrompt } };
|
|
188
|
+
module.exports = { createPersonalAgentLlm, SISTEMA_MOTOR, _test:{ clipped, compactMessages, compactTools, jsonObject, toMessage, buildPrompt, buildCompactPrompt, summarySection, taskSection, clipMiddle } };
|
package/lib/sentinela.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ⌁ ts sentinela — vigia DETERMINÍSTICO (o "cron --no-agent" do TS).
|
|
2
2
|
// Insight r/hermesagent: "se a lógica de aprovar/reprovar cabe num if, você NÃO precisa de um agente."
|
|
3
3
|
// Cada check roda um comando de shell, avalia a saída por REGRA (exit0/contém/sem/igual) — ZERO token.
|
|
4
|
-
// Só ESCALA pra IA (diagnose) quando o check FALHA e --escalar está ligado.
|
|
4
|
+
// Só ESCALA pra IA (diagnose) quando o check FALHA e --escalar está ligado. O alerta sai no terminal.
|
|
5
5
|
'use strict';
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const os = require('os');
|
|
@@ -92,7 +92,7 @@ function add(def) {
|
|
|
92
92
|
regra: def.regra || { tipo: 'exit0' },
|
|
93
93
|
avisar: def.avisar || 'falha', // 'falha' | 'sempre' | 'mudanca'
|
|
94
94
|
escalar: !!def.escalar || !!def.autofix, // escala pro diagnose quando falha (autofix implica escalar)
|
|
95
|
-
autofix: !!def.autofix, // propõe o conserto
|
|
95
|
+
autofix: !!def.autofix, // propõe o conserto, aplica só com aprovação no terminal e re-valida
|
|
96
96
|
criado: Date.now(),
|
|
97
97
|
proximo: ev.ms ? Date.now() : 0, // cron: 0 = decide por horário
|
|
98
98
|
ultimo: null, // { ts, ok, motivo, hash }
|