@openlife/cli 1.7.10 → 1.7.11
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/dist/index.js +26 -6
- package/dist/memory/MemoryProviderRegistry.js +5 -1
- package/dist/memory/MempalaceProvider.js +53 -5
- package/dist/memory/OmniMemory.js +9 -5
- package/dist/orchestrator/Brain.js +118 -11
- package/dist/orchestrator/Gatekeeper.js +46 -51
- package/dist/orchestrator/Gateway.js +86 -7
- package/dist/orchestrator/RuntimePolicy.js +28 -3
- package/dist/orchestrator/SquadCreator.js +15 -11
- package/dist/test_gateway_fast_ack.js +66 -0
- package/dist/test_no_automatic_messages.js +64 -0
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -537,11 +537,10 @@ program
|
|
|
537
537
|
.action(async (mensagemArgs, options) => {
|
|
538
538
|
const mensagem = mensagemArgs.join(' ');
|
|
539
539
|
console.log(`\n> Você: "${mensagem}"\n`);
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
540
|
+
// Hermes parity: `ask` must send the user's message to the agent runtime.
|
|
541
|
+
// Conversational shortcuts such as design import/generate belong to explicit
|
|
542
|
+
// subcommands, not to the main chat path, otherwise OpenLife appears to
|
|
543
|
+
// "answer" without invoking the configured GPT-5.5 executor.
|
|
545
544
|
const timeoutMs = Number(process.env.OPENLIFE_ASK_TIMEOUT_MS || 90000);
|
|
546
545
|
const classifier = new IntentClassifier_1.IntentClassifier();
|
|
547
546
|
const { Gatekeeper } = require('./orchestrator/Gatekeeper');
|
|
@@ -1397,7 +1396,28 @@ program
|
|
|
1397
1396
|
program
|
|
1398
1397
|
.command('update')
|
|
1399
1398
|
.description('Atualiza dependências, recompila o core e valida status do sistema (modo dev)')
|
|
1400
|
-
.
|
|
1399
|
+
.option('--global', 'Self-update via npm global install (npm i -g @openlife/cli@latest)')
|
|
1400
|
+
.action(async (opts) => {
|
|
1401
|
+
if (opts.global) {
|
|
1402
|
+
const { execFileSync } = require('child_process');
|
|
1403
|
+
try {
|
|
1404
|
+
const current = require('../package.json').version;
|
|
1405
|
+
const latest = String(execFileSync('npm', ['view', '@openlife/cli', 'version'], { encoding: 'utf-8' })).trim();
|
|
1406
|
+
if (latest === current) {
|
|
1407
|
+
console.log(JSON.stringify({ ok: true, status: 'up-to-date', version: current }));
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
console.log(`🔄 Self-update ${current} → ${latest}`);
|
|
1411
|
+
execFileSync('npm', ['install', '-g', '@openlife/cli@latest'], { stdio: 'inherit' });
|
|
1412
|
+
const after = String(execFileSync('openlife', ['--version'], { encoding: 'utf-8' })).trim();
|
|
1413
|
+
console.log(JSON.stringify({ ok: true, from: current, to: after }));
|
|
1414
|
+
}
|
|
1415
|
+
catch (e) {
|
|
1416
|
+
console.error(JSON.stringify({ ok: false, error: 'self_update_failed', detail: errMsg(e) }));
|
|
1417
|
+
process.exitCode = 1;
|
|
1418
|
+
}
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1401
1421
|
console.log('🔄 OPEN-LIFE update: npm install + build + system status');
|
|
1402
1422
|
try {
|
|
1403
1423
|
const { stdout, stderr } = await exec('npm install && npm run build && node bin/openlife.js system status', { cwd: process.cwd(), maxBuffer: 1024 * 1024 * 10 });
|
|
@@ -11,11 +11,15 @@ class MemoryProviderRegistry {
|
|
|
11
11
|
constructor() {
|
|
12
12
|
this.providers = {
|
|
13
13
|
local: new LocalMemoryProvider_1.LocalMemoryProvider(),
|
|
14
|
-
mempalace: new MempalaceProvider_1.MempalaceProvider(),
|
|
15
14
|
mem0: new Mem0Provider_1.Mem0Provider(),
|
|
16
15
|
'zep-graphiti': new ZepGraphitiProvider_1.ZepGraphitiProvider(),
|
|
17
16
|
'redis-ams': new RedisAgentMemoryProvider_1.RedisAgentMemoryProvider()
|
|
18
17
|
};
|
|
18
|
+
// MemPalace é opcional: registra apenas se o binário Python existir.
|
|
19
|
+
// Evita shell-out por turno em runtimes (Railway) que não têm mempalace instalado.
|
|
20
|
+
if (MempalaceProvider_1.MempalaceProvider.isAvailable()) {
|
|
21
|
+
this.providers.mempalace = new MempalaceProvider_1.MempalaceProvider();
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
get(name) {
|
|
21
25
|
return this.providers[name] || null;
|
|
@@ -35,21 +35,69 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MempalaceProvider = void 0;
|
|
37
37
|
const child_process = __importStar(require("child_process"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
38
41
|
const util_1 = require("util");
|
|
39
|
-
const
|
|
42
|
+
const execFile = (0, util_1.promisify)(child_process.execFile);
|
|
43
|
+
const DEFAULT_BIN = '~/.venvs/mempalace/bin/mempalace';
|
|
44
|
+
function expandHome(p) {
|
|
45
|
+
if (!p)
|
|
46
|
+
return p;
|
|
47
|
+
if (p === '~')
|
|
48
|
+
return os.homedir();
|
|
49
|
+
if (p.startsWith('~/'))
|
|
50
|
+
return path.join(os.homedir(), p.slice(2));
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* MemPalace é uma integração OPCIONAL. Em produção (Railway) o binário Python
|
|
55
|
+
* normalmente não existe; tentar shell-out adiciona latência e ruído de log a
|
|
56
|
+
* cada turno de conversa. `isAvailable()` resolve o path uma única vez e
|
|
57
|
+
* retorna false silenciosamente quando o binário não está instalado, para que
|
|
58
|
+
* o MemoryProviderRegistry possa pular o registro sem custo.
|
|
59
|
+
*/
|
|
40
60
|
class MempalaceProvider {
|
|
41
61
|
mempalacePath;
|
|
42
|
-
|
|
62
|
+
static cachedAvailability = null;
|
|
63
|
+
static cachedPath = null;
|
|
64
|
+
constructor(mempalacePath = process.env.MEMPALACE_BIN_PATH || DEFAULT_BIN) {
|
|
43
65
|
this.mempalacePath = mempalacePath;
|
|
44
66
|
}
|
|
45
67
|
name() {
|
|
46
68
|
return 'mempalace';
|
|
47
69
|
}
|
|
70
|
+
static isAvailable(rawPath) {
|
|
71
|
+
const configured = (rawPath ?? process.env.MEMPALACE_BIN_PATH ?? DEFAULT_BIN).trim();
|
|
72
|
+
if (configured === '' || configured.toLowerCase() === 'off' || configured.toLowerCase() === 'disabled') {
|
|
73
|
+
MempalaceProvider.cachedAvailability = false;
|
|
74
|
+
MempalaceProvider.cachedPath = null;
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (MempalaceProvider.cachedAvailability !== null && MempalaceProvider.cachedPath === configured) {
|
|
78
|
+
return MempalaceProvider.cachedAvailability;
|
|
79
|
+
}
|
|
80
|
+
const resolved = expandHome(configured);
|
|
81
|
+
let ok = false;
|
|
82
|
+
try {
|
|
83
|
+
ok = fs.existsSync(resolved) && fs.statSync(resolved).isFile();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
ok = false;
|
|
87
|
+
}
|
|
88
|
+
MempalaceProvider.cachedAvailability = ok;
|
|
89
|
+
MempalaceProvider.cachedPath = configured;
|
|
90
|
+
return ok;
|
|
91
|
+
}
|
|
92
|
+
static resetAvailabilityCache() {
|
|
93
|
+
MempalaceProvider.cachedAvailability = null;
|
|
94
|
+
MempalaceProvider.cachedPath = null;
|
|
95
|
+
}
|
|
48
96
|
async search(input) {
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
if (!MempalaceProvider.isAvailable(this.mempalacePath))
|
|
98
|
+
return [];
|
|
51
99
|
try {
|
|
52
|
-
const { stdout } = await
|
|
100
|
+
const { stdout } = await execFile(expandHome(this.mempalacePath), ['search', input.query], { timeout: 5000 });
|
|
53
101
|
if (!stdout.trim())
|
|
54
102
|
return [];
|
|
55
103
|
return [{
|
|
@@ -39,6 +39,7 @@ const util_1 = require("util");
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const LocalMemoryProvider_1 = require("./LocalMemoryProvider");
|
|
42
|
+
const MempalaceProvider_1 = require("./MempalaceProvider");
|
|
42
43
|
const MemoryOrchestrator_1 = require("./MemoryOrchestrator");
|
|
43
44
|
const ToolsetGuard_1 = require("../orchestrator/toolset/ToolsetGuard");
|
|
44
45
|
const exec = (0, util_1.promisify)(child_process.exec);
|
|
@@ -57,20 +58,23 @@ class OmniMemory {
|
|
|
57
58
|
if (hits.length) {
|
|
58
59
|
return hits.map(hit => `- [${hit.provider}] ${hit.record.summary || hit.record.content}`).join('\n');
|
|
59
60
|
}
|
|
61
|
+
if (!MempalaceProvider_1.MempalaceProvider.isAvailable(this.mempalacePath)) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
60
64
|
console.log(`[OMNI-MEMORY] Buscando no MemPalace: "${query}"...`);
|
|
61
65
|
try {
|
|
62
66
|
const safeQuery = query.replace(/"/g, '\\"');
|
|
63
67
|
const command = `bash -c "${this.mempalacePath} search \\\"${safeQuery}\\\""`;
|
|
64
|
-
const
|
|
68
|
+
const timeoutMs = Number(process.env.OPENLIFE_MEMORY_SEARCH_TIMEOUT_MS || 1500);
|
|
69
|
+
const { stdout } = await exec(command, { timeout: timeoutMs });
|
|
65
70
|
if (!stdout.trim() || stdout.includes("No results found")) {
|
|
66
|
-
return "
|
|
71
|
+
return "";
|
|
67
72
|
}
|
|
68
73
|
const citation = `\n\n[RAG de Evidências: Buscado em ${new Date().toISOString()}]`;
|
|
69
74
|
return stdout + citation;
|
|
70
75
|
}
|
|
71
|
-
catch (
|
|
72
|
-
|
|
73
|
-
return "Busca na Omni-Memory concluída com alertas ou sem resultados diretos.";
|
|
76
|
+
catch (_error) {
|
|
77
|
+
return "";
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
async saveFact(fact, metadata, namespace) {
|
|
@@ -36,18 +36,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.Brain = void 0;
|
|
39
|
+
exports.Brain = exports.CodexTimeoutError = void 0;
|
|
40
40
|
const child_process = __importStar(require("child_process"));
|
|
41
41
|
const util_1 = require("util");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
42
44
|
const openai_1 = __importDefault(require("openai"));
|
|
43
45
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
44
46
|
const generative_ai_1 = require("@google/generative-ai");
|
|
45
47
|
const ModelManager_1 = require("./ModelManager");
|
|
46
48
|
const ToolsetGuard_1 = require("./toolset/ToolsetGuard");
|
|
47
49
|
const execFile = (0, util_1.promisify)(child_process.execFile);
|
|
48
|
-
const DEFAULT_MODEL_TIMEOUT_MS = Number(process.env.OPENLIFE_MODEL_TIMEOUT_MS ||
|
|
50
|
+
const DEFAULT_MODEL_TIMEOUT_MS = Number(process.env.OPENLIFE_MODEL_TIMEOUT_MS || 60000);
|
|
49
51
|
const REASONING_MODE = String(process.env.OPENLIFE_REASONING_MODE || 'errors').toLowerCase(); // off | errors | always
|
|
52
|
+
// Conversational fast path must fail fast so the Gateway can surface a
|
|
53
|
+
// timeout-aware reply to the user. Deep engineering missions keep the full
|
|
54
|
+
// 60s budget. Both are env-overridable.
|
|
55
|
+
function getCodexFastTimeoutMs() {
|
|
56
|
+
return Number(process.env.OPENLIFE_CODEX_FAST_TIMEOUT_MS || 30000);
|
|
57
|
+
}
|
|
58
|
+
function getCodexDeepTimeoutMs() {
|
|
59
|
+
return Number(process.env.OPENLIFE_CODEX_DEEP_TIMEOUT_MS || DEFAULT_MODEL_TIMEOUT_MS);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Typed Codex CLI timeout. Callers (Gateway, Gatekeeper) can distinguish a
|
|
63
|
+
* real timeout from a generic provider failure and present a clearer message.
|
|
64
|
+
* The `depth` attribute marks whether the fast or deep budget fired.
|
|
65
|
+
*/
|
|
66
|
+
class CodexTimeoutError extends Error {
|
|
67
|
+
depth;
|
|
68
|
+
timeoutMs;
|
|
69
|
+
model;
|
|
70
|
+
constructor(depth, timeoutMs, model) {
|
|
71
|
+
super(`CODEX_TIMEOUT_${depth.toUpperCase()}_${timeoutMs}MS_${model || 'default'}`);
|
|
72
|
+
this.name = 'CodexTimeoutError';
|
|
73
|
+
this.depth = depth;
|
|
74
|
+
this.timeoutMs = timeoutMs;
|
|
75
|
+
this.model = model || 'default';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.CodexTimeoutError = CodexTimeoutError;
|
|
50
79
|
class Brain {
|
|
80
|
+
static codexQueue = Promise.resolve();
|
|
51
81
|
openai = null;
|
|
52
82
|
anthropic = null;
|
|
53
83
|
geminiApi = null;
|
|
@@ -90,6 +120,15 @@ class Brain {
|
|
|
90
120
|
async think(systemPrompt, userMessage) {
|
|
91
121
|
const config = this.modelManager.getModelConfig();
|
|
92
122
|
const modelsToTry = [config.primary, ...config.fallbacks].filter((model, index, arr) => arr.findIndex(m => m.raw === model.raw) === index);
|
|
123
|
+
return this.runModelChain(systemPrompt, userMessage, modelsToTry, 'deep');
|
|
124
|
+
}
|
|
125
|
+
/** Fast conversational path. Keeps the configured model chain order intact. */
|
|
126
|
+
async thinkFast(systemPrompt, userMessage) {
|
|
127
|
+
const config = this.modelManager.getModelConfig();
|
|
128
|
+
const models = [config.primary, ...config.fallbacks].filter((model, index, arr) => arr.findIndex(m => m.raw === model.raw) === index);
|
|
129
|
+
return this.runModelChain(systemPrompt, userMessage, models, 'fast');
|
|
130
|
+
}
|
|
131
|
+
async runModelChain(systemPrompt, userMessage, modelsToTry, depth = 'deep') {
|
|
93
132
|
const failures = [];
|
|
94
133
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
95
134
|
const modelIdent = modelsToTry[i];
|
|
@@ -101,7 +140,7 @@ class Brain {
|
|
|
101
140
|
out = await this.thinkWithOpenAIAPI(systemPrompt, userMessage, modelIdent.name);
|
|
102
141
|
break;
|
|
103
142
|
case 'openai-cli':
|
|
104
|
-
out = await this.thinkWithOpenAICLI(systemPrompt, userMessage, modelIdent.name);
|
|
143
|
+
out = await this.thinkWithOpenAICLI(systemPrompt, userMessage, modelIdent.name, depth);
|
|
105
144
|
break;
|
|
106
145
|
case 'anthropic':
|
|
107
146
|
out = await this.thinkWithAnthropic(systemPrompt, userMessage, modelIdent.name);
|
|
@@ -139,10 +178,24 @@ class Brain {
|
|
|
139
178
|
}
|
|
140
179
|
}
|
|
141
180
|
const concise = failures.slice(-3).map(f => `- ${f.model}: ${f.reason}`).join('\n');
|
|
181
|
+
const strictGpt55Only = modelsToTry.length === 1 && modelsToTry[0]?.raw === 'openai-cli/gpt-5.5';
|
|
182
|
+
if (strictGpt55Only) {
|
|
183
|
+
return [
|
|
184
|
+
'Executor GPT-5.5 indisponível no momento.',
|
|
185
|
+
'',
|
|
186
|
+
'Falhas recentes:',
|
|
187
|
+
concise,
|
|
188
|
+
'',
|
|
189
|
+
'Ação recomendada:',
|
|
190
|
+
'1) Reautentique o Codex OAuth no mesmo runtime onde o OpenLife está rodando.',
|
|
191
|
+
'2) Valide com: codex login status',
|
|
192
|
+
'3) Valide execução real com: codex exec --model gpt-5.5 "responda apenas OK"'
|
|
193
|
+
].join('\n');
|
|
194
|
+
}
|
|
142
195
|
const guidance = [
|
|
143
|
-
'1)
|
|
144
|
-
'2)
|
|
145
|
-
'3) Rode: openlife doctor e
|
|
196
|
+
'1) Valide as credenciais/binários dos provedores configurados.',
|
|
197
|
+
'2) Ajuste models.json apenas se a política do produto permitir fallbacks.',
|
|
198
|
+
'3) Rode: openlife doctor e openlife models status.'
|
|
146
199
|
].join('\n');
|
|
147
200
|
const summary = REASONING_MODE !== 'off'
|
|
148
201
|
? `\n\nResumo operacional:\n- Objetivo: responder com robustez por fallback.\n- Estratégia: rotação sequencial de provedores.\n- Resultado: todos os provedores tentados falharam.`
|
|
@@ -193,19 +246,73 @@ class Brain {
|
|
|
193
246
|
throw this.formatProviderError('openai-api', model, err, { keyEnvVar: 'OPENAI_API_KEY', expectedKeyPrefix: 'sk-' });
|
|
194
247
|
}
|
|
195
248
|
}
|
|
196
|
-
async thinkWithOpenAICLI(systemPrompt, userMessage, model) {
|
|
249
|
+
async thinkWithOpenAICLI(systemPrompt, userMessage, model, depth = 'deep') {
|
|
197
250
|
(0, ToolsetGuard_1.assertToolsetAllowed)('delegation', 'Brain.thinkWithOpenAICLI');
|
|
198
|
-
const
|
|
251
|
+
const outputFile = path.join(process.cwd(), '.openlife', `codex-last-message-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`);
|
|
252
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
253
|
+
const args = ['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--color', 'never', '--output-last-message', outputFile];
|
|
254
|
+
if (String(process.env.OPENLIFE_CODEX_EPHEMERAL || 'true').toLowerCase() !== 'false')
|
|
255
|
+
args.push('--ephemeral');
|
|
199
256
|
if (model && model !== 'default')
|
|
200
257
|
args.push('--model', model);
|
|
201
|
-
|
|
258
|
+
const prompt = depth === 'fast'
|
|
259
|
+
? [
|
|
260
|
+
'Você é Lara, agente conversacional do OpenLife.',
|
|
261
|
+
'Responda diretamente em português natural e conciso.',
|
|
262
|
+
'Não use ferramentas, não leia arquivos e não execute comandos para responder esta mensagem.',
|
|
263
|
+
'Se não souber algo específico, seja honesta e diga o que falta.',
|
|
264
|
+
'',
|
|
265
|
+
`Mensagem: ${userMessage}`
|
|
266
|
+
].join('\n')
|
|
267
|
+
: `${systemPrompt}\n\nMensagem: ${userMessage}`;
|
|
268
|
+
args.push(prompt);
|
|
269
|
+
const timeoutMs = depth === 'fast' ? getCodexFastTimeoutMs() : getCodexDeepTimeoutMs();
|
|
270
|
+
// Deep missions remain serialized through the static queue to avoid
|
|
271
|
+
// contending Codex CLI invocations. Fast conversational calls bypass
|
|
272
|
+
// the queue so a long-running deep mission can not block a greeting.
|
|
273
|
+
let release = () => { };
|
|
274
|
+
if (depth === 'deep') {
|
|
275
|
+
const previous = Brain.codexQueue;
|
|
276
|
+
Brain.codexQueue = new Promise((resolve) => { release = resolve; });
|
|
277
|
+
await previous;
|
|
278
|
+
}
|
|
202
279
|
try {
|
|
203
|
-
const
|
|
204
|
-
|
|
280
|
+
const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
281
|
+
const command = process.platform === 'win32' ? 'codex' : 'timeout';
|
|
282
|
+
const commandArgs = process.platform === 'win32'
|
|
283
|
+
? args
|
|
284
|
+
: ['-k', '2s', `${timeoutSeconds}s`, 'codex', ...args];
|
|
285
|
+
const { stdout } = await execFile(command, commandArgs, { maxBuffer: 1024 * 1024 * 10, timeout: timeoutMs + 3000, killSignal: 'SIGKILL' });
|
|
286
|
+
const lastMessage = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf-8').trim() : '';
|
|
287
|
+
try {
|
|
288
|
+
fs.unlinkSync(outputFile);
|
|
289
|
+
}
|
|
290
|
+
catch { /* ignore cleanup */ }
|
|
291
|
+
const finalText = lastMessage || stdout.trim();
|
|
292
|
+
if (!finalText)
|
|
293
|
+
throw new Error(`EMPTY_CODEX_RESPONSE_${model || 'default'}`);
|
|
294
|
+
return finalText;
|
|
205
295
|
}
|
|
206
296
|
catch (err) {
|
|
297
|
+
try {
|
|
298
|
+
if (fs.existsSync(outputFile))
|
|
299
|
+
fs.unlinkSync(outputFile);
|
|
300
|
+
}
|
|
301
|
+
catch { /* ignore cleanup */ }
|
|
302
|
+
// execFile signals a timeout via either ETIMEDOUT or by killing the
|
|
303
|
+
// child (signal !== null). Surface a typed error so callers can
|
|
304
|
+
// present a depth-aware message.
|
|
305
|
+
const e = err;
|
|
306
|
+
const errCode = e.code;
|
|
307
|
+
const isTimeout = errCode === 'ETIMEDOUT' || errCode === 124 || errCode === '124' || (e?.killed === true && !!e?.signal);
|
|
308
|
+
if (isTimeout) {
|
|
309
|
+
throw new CodexTimeoutError(depth, timeoutMs, model);
|
|
310
|
+
}
|
|
207
311
|
throw this.formatProviderError('openai-cli', model, err);
|
|
208
312
|
}
|
|
313
|
+
finally {
|
|
314
|
+
release();
|
|
315
|
+
}
|
|
209
316
|
}
|
|
210
317
|
async thinkWithAnthropic(systemPrompt, userMessage, model) {
|
|
211
318
|
if (!this.anthropic)
|
|
@@ -229,78 +229,73 @@ class Gatekeeper {
|
|
|
229
229
|
return direct;
|
|
230
230
|
}
|
|
231
231
|
let finalResponse = "";
|
|
232
|
-
const recoveredContext = await this.conversationMemory.recoverContext(userId, userInput);
|
|
233
232
|
switch (task.intent) {
|
|
234
|
-
case IntentClassifier_1.TaskIntent.KNOWLEDGE_RETRIEVAL:
|
|
235
|
-
|
|
233
|
+
case IntentClassifier_1.TaskIntent.KNOWLEDGE_RETRIEVAL: {
|
|
234
|
+
const useMemoryContext = this.shouldUseMemoryContext(userInput);
|
|
235
|
+
const recoveredContext = useMemoryContext
|
|
236
|
+
? await this.conversationMemory.recoverContext(userId, userInput)
|
|
237
|
+
: { recentHistory: this.getRecentSessionHistory(userId), memorySnippet: '', autoRecovered: false };
|
|
238
|
+
finalResponse = await this.handleFastPath(userInput, recoveredContext.recentHistory, recoveredContext.memorySnippet, useMemoryContext);
|
|
236
239
|
break;
|
|
240
|
+
}
|
|
237
241
|
case IntentClassifier_1.TaskIntent.ENGINEERING_BUILD:
|
|
238
|
-
case IntentClassifier_1.TaskIntent.RESEARCH_ANALYSIS:
|
|
242
|
+
case IntentClassifier_1.TaskIntent.RESEARCH_ANALYSIS: {
|
|
243
|
+
const recoveredContext = await this.conversationMemory.recoverContext(userId, userInput);
|
|
239
244
|
finalResponse = await this.handleComplexPath(userInput, task, recoveredContext.recentHistory, userId, options?.mode);
|
|
240
245
|
break;
|
|
246
|
+
}
|
|
241
247
|
default:
|
|
242
|
-
finalResponse =
|
|
248
|
+
finalResponse = await this.handleFastPath(userInput, this.getRecentSessionHistory(userId), '', false);
|
|
243
249
|
}
|
|
244
250
|
this.sessionManager.addMessage(userId, 'agent', finalResponse);
|
|
245
251
|
return finalResponse;
|
|
246
252
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
253
|
+
getRecentSessionHistory(userId) {
|
|
254
|
+
const session = this.sessionManager.getSession(userId);
|
|
255
|
+
return session.history
|
|
256
|
+
.slice(-4)
|
|
257
|
+
.map((h) => `${h.role}: ${h.content}`)
|
|
258
|
+
.join('\n');
|
|
259
|
+
}
|
|
260
|
+
shouldUseMemoryContext(input) {
|
|
261
|
+
const normalized = input.toLowerCase();
|
|
262
|
+
if (String(process.env.OPENLIFE_FASTPATH_MEMORY || 'auto').toLowerCase() === 'always')
|
|
263
|
+
return true;
|
|
264
|
+
if (String(process.env.OPENLIFE_FASTPATH_MEMORY || 'auto').toLowerCase() === 'off')
|
|
265
|
+
return false;
|
|
266
|
+
if (input.length > 240)
|
|
267
|
+
return true;
|
|
268
|
+
return /(lembra|memória|memoria|projeto|openlife|lara|aiobuilder|catalog|catálogo|railway|github|deploy|repo|arquivo|documento|histórico|historico)/i.test(normalized);
|
|
269
|
+
}
|
|
270
|
+
async handleFastPath(input, recentHistory, recoveredMemory, useMemoryContext) {
|
|
271
|
+
console.log(useMemoryContext
|
|
272
|
+
? "[FAST PATH] GPT-5.5 com contexto de memória seletivo..."
|
|
273
|
+
: "[FAST PATH] GPT-5.5 modo compacto sem OmniMemory...");
|
|
274
|
+
const searchResult = useMemoryContext ? (recoveredMemory || await this.memory.search(input)) : '';
|
|
250
275
|
const systemPrompt = `
|
|
251
|
-
|
|
252
|
-
${this.
|
|
253
|
-
|
|
254
|
-
Sua Identidade (IDENTITY):
|
|
255
|
-
${this.systemIdentity}
|
|
276
|
+
Identidade:
|
|
277
|
+
${this.systemIdentity.substring(0, 900)}
|
|
256
278
|
|
|
257
|
-
|
|
258
|
-
|
|
279
|
+
${searchResult ? `Contexto relevante:\n${searchResult.substring(0, 900)}\n` : ''}
|
|
280
|
+
Histórico recente:
|
|
281
|
+
${recentHistory.slice(-1200)}
|
|
259
282
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
-
|
|
265
|
-
- Você tem memória fluida. Baseie-se fortemente no [Histórico Recente da Sessão] para manter o contexto vivo. Responda DIRETAMENTE à última mensagem.
|
|
266
|
-
- Use a OmniMemory para informações profundas de projetos (como LARA, AIOBUILDER).
|
|
267
|
-
- Se for apenas um 'oi' inicial, saúde amigavelmente e pergunte como pode ajudar.
|
|
268
|
-
- NUNCA mencione tool calls, comandos internos, ou frases como "vou executar comando". Apenas responda naturalmente com o resultado final.
|
|
283
|
+
Instruções:
|
|
284
|
+
- Responda diretamente à última mensagem, em português natural.
|
|
285
|
+
- Seja rápido e conciso por padrão.
|
|
286
|
+
- Não mencione comandos internos, tool calls ou detalhes de execução.
|
|
287
|
+
- Se precisar de contexto profundo, diga objetivamente o que falta.
|
|
269
288
|
`;
|
|
270
|
-
const fastTimeoutMs = Number(process.env.OPENLIFE_FASTPATH_TIMEOUT_MS || 90000);
|
|
271
|
-
const degradedPrompt = `${this.systemIdentity}\n\nResponda de forma direta e curta, sem contexto extra.`;
|
|
272
289
|
try {
|
|
273
|
-
return await
|
|
274
|
-
this.brain.think(systemPrompt, input),
|
|
275
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`FAST_PATH_TIMEOUT_${fastTimeoutMs}`)), fastTimeoutMs))
|
|
276
|
-
]);
|
|
290
|
+
return await this.brain.thinkFast(systemPrompt, input);
|
|
277
291
|
}
|
|
278
292
|
catch (err) {
|
|
279
293
|
const code = (err instanceof Error ? err.message : String(err)) || 'FAST_PATH_FAILURE';
|
|
280
294
|
console.error(`[FAST PATH ERROR] ${code}`);
|
|
281
|
-
if (String(code).startsWith('FAST_PATH_TIMEOUT_')) {
|
|
282
|
-
try {
|
|
283
|
-
const retryTimeoutMs = Number(process.env.OPENLIFE_FASTPATH_RETRY_TIMEOUT_MS || 30000);
|
|
284
|
-
return await Promise.race([
|
|
285
|
-
this.brain.think(degradedPrompt, input),
|
|
286
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`FAST_PATH_RETRY_TIMEOUT_${retryTimeoutMs}`)), retryTimeoutMs))
|
|
287
|
-
]);
|
|
288
|
-
}
|
|
289
|
-
catch (retryErr) {
|
|
290
|
-
const retryCode = (retryErr instanceof Error ? retryErr.message : String(retryErr)) || 'FAST_PATH_RETRY_FAILURE';
|
|
291
|
-
return [
|
|
292
|
-
'OPENLIFE ONLINE ✅',
|
|
293
|
-
'Modo resiliente: primeira rota estourou tempo, tentei fallback enxuto.',
|
|
294
|
-
`Motivo técnico: ${retryCode}`,
|
|
295
|
-
'Ação: valide cadeia de modelos com `openlife models status` e aumente timeout com OPENLIFE_FASTPATH_TIMEOUT_MS.'
|
|
296
|
-
].join('\n');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
295
|
return [
|
|
300
|
-
'
|
|
301
|
-
'Modo seguro ativado: não vou inventar resposta quando o executor falhar.',
|
|
296
|
+
'Executor GPT-5.5 indisponível ou lento demais neste momento.',
|
|
302
297
|
`Motivo técnico: ${code}`,
|
|
303
|
-
'Ação:
|
|
298
|
+
'Ação: verifique OAuth do Codex e tente novamente.'
|
|
304
299
|
].join('\n');
|
|
305
300
|
}
|
|
306
301
|
}
|
|
@@ -415,16 +415,59 @@ class Gateway {
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
async processTextForTest(userId, text) {
|
|
418
|
-
|
|
418
|
+
const detailed = await this.processTextForTestDetailed(userId, text);
|
|
419
|
+
return detailed.finalText;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Test seam that captures the full ACK + final timeline for the fast-ACK
|
|
423
|
+
* pattern. Used by the regression test to assert the placeholder reply
|
|
424
|
+
* lands within the ACK budget even when downstream model processing is
|
|
425
|
+
* slow. Returns:
|
|
426
|
+
* - finalText: the user-visible final answer (edited ACK or last reply)
|
|
427
|
+
* - events: ordered timeline (kind = 'reply' | 'edit' | 'voice')
|
|
428
|
+
* - ackMs: ms until the first reply landed (null if none was sent)
|
|
429
|
+
* - finalMs: ms until the last event landed
|
|
430
|
+
*/
|
|
431
|
+
async processTextForTestDetailed(userId, text) {
|
|
432
|
+
const startTs = Date.now();
|
|
433
|
+
const events = [];
|
|
434
|
+
let ackMs = null;
|
|
435
|
+
let lastReplyText = '';
|
|
436
|
+
let lastEditText = null;
|
|
437
|
+
let messageIdCounter = 0;
|
|
438
|
+
let lastReplyMessageId = 0;
|
|
419
439
|
const testCtx = {
|
|
420
440
|
sendChatAction: async (_action) => { },
|
|
421
|
-
reply: async (message) => {
|
|
441
|
+
reply: async (message) => {
|
|
442
|
+
const tsMs = Date.now() - startTs;
|
|
443
|
+
events.push({ kind: 'reply', text: message, tsMs });
|
|
444
|
+
if (ackMs === null)
|
|
445
|
+
ackMs = tsMs;
|
|
446
|
+
lastReplyText = message;
|
|
447
|
+
lastReplyMessageId = ++messageIdCounter;
|
|
448
|
+
return { message_id: lastReplyMessageId };
|
|
449
|
+
},
|
|
422
450
|
sendVoice: async (_source, options) => {
|
|
423
|
-
|
|
451
|
+
const caption = options?.caption || '[voice_sent]';
|
|
452
|
+
const tsMs = Date.now() - startTs;
|
|
453
|
+
events.push({ kind: 'voice', text: caption, tsMs });
|
|
454
|
+
lastReplyText = caption;
|
|
455
|
+
return { message_id: ++messageIdCounter };
|
|
456
|
+
},
|
|
457
|
+
chat: { id: userId },
|
|
458
|
+
telegram: {
|
|
459
|
+
editMessageText: async (_chatId, _messageId, _inlineId, edited) => {
|
|
460
|
+
const tsMs = Date.now() - startTs;
|
|
461
|
+
events.push({ kind: 'edit', text: edited, tsMs });
|
|
462
|
+
lastEditText = edited;
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
424
465
|
}
|
|
425
466
|
};
|
|
426
467
|
await this.processInput(testCtx, userId, text);
|
|
427
|
-
|
|
468
|
+
const finalText = lastEditText ?? lastReplyText;
|
|
469
|
+
const finalMs = events.length ? events[events.length - 1].tsMs : 0;
|
|
470
|
+
return { finalText, events, ackMs, finalMs };
|
|
428
471
|
}
|
|
429
472
|
async processImageForTest(userId, imagePath, caption = 'Descreva esta imagem.') {
|
|
430
473
|
(0, ToolsetGuard_1.assertToolsetAllowed)('vision', 'Gateway.processImageForTest');
|
|
@@ -442,6 +485,42 @@ class Gateway {
|
|
|
442
485
|
await this.safeReply(ctx, 'Acesso negado por policy de segurança deste bot.');
|
|
443
486
|
return;
|
|
444
487
|
}
|
|
488
|
+
// Fast ACK pattern: fire a placeholder reply in parallel with the actual
|
|
489
|
+
// classifier+gatekeeper work. When the work completes, edit the ACK with
|
|
490
|
+
// the real answer (Telegraf editMessageText). Falls back to a fresh
|
|
491
|
+
// reply when the surface does not expose `chat`/`telegram` (webhook mock
|
|
492
|
+
// and similar internal callers).
|
|
493
|
+
const ackText = '🔎 Recebido — processando…';
|
|
494
|
+
let ackMessageId = null;
|
|
495
|
+
const ackPromise = (async () => {
|
|
496
|
+
try {
|
|
497
|
+
const result = await ctx.reply(ackText);
|
|
498
|
+
if (result && typeof result === 'object' && 'message_id' in result) {
|
|
499
|
+
const mid = result.message_id;
|
|
500
|
+
if (typeof mid === 'number')
|
|
501
|
+
ackMessageId = mid;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch (ackErr) {
|
|
505
|
+
console.error('[GATEWAY] Falha ao enviar ACK rápido:', ackErr);
|
|
506
|
+
}
|
|
507
|
+
})();
|
|
508
|
+
const sendFinal = async (finalText) => {
|
|
509
|
+
await ackPromise;
|
|
510
|
+
if (ackMessageId !== null &&
|
|
511
|
+
ctx.chat &&
|
|
512
|
+
ctx.telegram &&
|
|
513
|
+
typeof ctx.telegram.editMessageText === 'function') {
|
|
514
|
+
try {
|
|
515
|
+
await ctx.telegram.editMessageText(ctx.chat.id, ackMessageId, undefined, finalText);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
catch (editErr) {
|
|
519
|
+
console.error('[GATEWAY] editMessageText falhou, enviando nova mensagem:', editErr);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
await this.safeReply(ctx, finalText);
|
|
523
|
+
};
|
|
445
524
|
const stopTyping = this.startTypingIndicator(ctx);
|
|
446
525
|
const trace = [];
|
|
447
526
|
try {
|
|
@@ -459,11 +538,11 @@ class Gateway {
|
|
|
459
538
|
}
|
|
460
539
|
catch (ttsError) {
|
|
461
540
|
console.log("[GATEWAY] Fallback para texto (Erro no TTS).", ttsError);
|
|
462
|
-
await
|
|
541
|
+
await sendFinal(finalResponse);
|
|
463
542
|
}
|
|
464
543
|
}
|
|
465
544
|
else {
|
|
466
|
-
await
|
|
545
|
+
await sendFinal(finalResponse);
|
|
467
546
|
}
|
|
468
547
|
}
|
|
469
548
|
catch (err) {
|
|
@@ -471,7 +550,7 @@ class Gateway {
|
|
|
471
550
|
const errMsg = this.reasoningMode !== 'off'
|
|
472
551
|
? `${trace.join('\n')}\n\n❌ error: "${err instanceof Error ? err.message : String(err)}"\n\nErro no córtex ao processar a solicitação.`
|
|
473
552
|
: "Erro no córtex ao processar a solicitação.";
|
|
474
|
-
await
|
|
553
|
+
await sendFinal(errMsg);
|
|
475
554
|
}
|
|
476
555
|
finally {
|
|
477
556
|
stopTyping();
|
|
@@ -46,11 +46,14 @@ class RuntimePolicy {
|
|
|
46
46
|
this.runtimeHealth = new RuntimeHealthMonitor_1.RuntimeHealthMonitor();
|
|
47
47
|
}
|
|
48
48
|
decide(intent, explicitExecutors = []) {
|
|
49
|
+
const strictModelExecutors = this.getStrictModelExecutors();
|
|
49
50
|
const base = explicitExecutors.length
|
|
50
51
|
? explicitExecutors
|
|
51
|
-
:
|
|
52
|
-
?
|
|
53
|
-
:
|
|
52
|
+
: strictModelExecutors.length
|
|
53
|
+
? strictModelExecutors
|
|
54
|
+
: intent === 'RESEARCH_ANALYSIS'
|
|
55
|
+
? ['gemini', 'claude', 'codex']
|
|
56
|
+
: ['codex', 'claude', 'gemini'];
|
|
54
57
|
const allowedRaw = (process.env.OPENLIFE_ALLOWED_LLM_EXECUTORS || '').trim().toLowerCase();
|
|
55
58
|
const allowed = new Set();
|
|
56
59
|
if (allowedRaw) {
|
|
@@ -76,6 +79,28 @@ class RuntimePolicy {
|
|
|
76
79
|
: 'Todos os executores da cadeia configurada estão indisponíveis no momento.'
|
|
77
80
|
};
|
|
78
81
|
}
|
|
82
|
+
getStrictModelExecutors() {
|
|
83
|
+
try {
|
|
84
|
+
const modelsPath = path.join(process.cwd(), 'models.json');
|
|
85
|
+
if (!fs.existsSync(modelsPath))
|
|
86
|
+
return [];
|
|
87
|
+
const cfg = JSON.parse(fs.readFileSync(modelsPath, 'utf-8'));
|
|
88
|
+
const chain = [cfg.primary, ...(cfg.fallbacks || [])].filter(Boolean);
|
|
89
|
+
if (chain.length !== 1)
|
|
90
|
+
return [];
|
|
91
|
+
const provider = chain[0].provider || '';
|
|
92
|
+
if (provider === 'openai-cli')
|
|
93
|
+
return ['codex'];
|
|
94
|
+
if (provider === 'gemini-cli' || provider === 'gemini-api')
|
|
95
|
+
return ['gemini'];
|
|
96
|
+
if (provider === 'anthropic-api')
|
|
97
|
+
return ['claude'];
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
79
104
|
recordResult(executor, ok, reason) {
|
|
80
105
|
this.executorHealth.set(executor, ok, reason);
|
|
81
106
|
if (ok) {
|
|
@@ -171,17 +171,21 @@ class SquadCreator {
|
|
|
171
171
|
if (frontmatter.id && frontmatter.id !== squadId) {
|
|
172
172
|
errors.push(`frontmatter id '${frontmatter.id}' does not match directory '${squadId}'`);
|
|
173
173
|
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
174
|
+
// Imported squads (lara-squads-import, obsidian-mirror, etc.) reference
|
|
175
|
+
// agents/tasks from the global .catalog/{agents,skills}/ pool, not squad-local.
|
|
176
|
+
const isImported = typeof frontmatter.source === 'string' && frontmatter.source.endsWith('-import');
|
|
177
|
+
if (!isImported) {
|
|
178
|
+
const componentRefs = this.parseComponentRefs(content);
|
|
179
|
+
for (const refPath of componentRefs.agents) {
|
|
180
|
+
const full = path.join(squadDir, 'agents', refPath);
|
|
181
|
+
if (!fs.existsSync(full))
|
|
182
|
+
warnings.push(`referenced agent not found: agents/${refPath}`);
|
|
183
|
+
}
|
|
184
|
+
for (const refPath of componentRefs.tasks) {
|
|
185
|
+
const full = path.join(squadDir, 'tasks', refPath);
|
|
186
|
+
if (!fs.existsSync(full))
|
|
187
|
+
warnings.push(`referenced task not found: tasks/${refPath}`);
|
|
188
|
+
}
|
|
185
189
|
}
|
|
186
190
|
return { ok: errors.length === 0, squadId, errors, warnings };
|
|
187
191
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const assert = __importStar(require("assert"));
|
|
37
|
+
const Gateway_1 = require("./orchestrator/Gateway");
|
|
38
|
+
async function main() {
|
|
39
|
+
process.env.OPENLIFE_TELEGRAM_ALLOWED_USER_ID = '';
|
|
40
|
+
process.env.OPENLIFE_ENABLE_TTS = 'false';
|
|
41
|
+
const gateway = new Gateway_1.Gateway();
|
|
42
|
+
gateway.classifier = {
|
|
43
|
+
classify: async (_text) => ({ intent: 'KNOWLEDGE_RETRIEVAL', budget: 0.1 })
|
|
44
|
+
};
|
|
45
|
+
gateway.gatekeeper = {
|
|
46
|
+
routeTask: async (_task, _text, _userId) => {
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
48
|
+
return 'FINAL_AGENT_RESPONSE_FROM_GPT55_PATH';
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const started = Date.now();
|
|
52
|
+
const result = await gateway.processTextForTestDetailed('1344110010', 'ola');
|
|
53
|
+
const totalMs = Date.now() - started;
|
|
54
|
+
assert.ok(result.ackMs !== null, 'ACK_NOT_SENT');
|
|
55
|
+
assert.ok(result.ackMs < 500, `ACK_TOO_SLOW_${result.ackMs}`);
|
|
56
|
+
assert.ok(result.events.length >= 2, `EXPECTED_ACK_AND_FINAL_EVENTS_${JSON.stringify(result.events)}`);
|
|
57
|
+
assert.strictEqual(result.events[0].kind, 'reply', 'FIRST_EVENT_SHOULD_BE_ACK_REPLY');
|
|
58
|
+
assert.ok(result.events[0].text.includes('processando'), `ACK_TEXT_UNEXPECTED_${result.events[0].text}`);
|
|
59
|
+
assert.strictEqual(result.finalText, 'FINAL_AGENT_RESPONSE_FROM_GPT55_PATH', `FINAL_TEXT_UNEXPECTED_${result.finalText}`);
|
|
60
|
+
assert.ok(totalMs >= 1100, 'TEST_STUB_DID_NOT_SIMULATE_SLOW_PATH');
|
|
61
|
+
console.log('TEST_GATEWAY_FAST_ACK_OK');
|
|
62
|
+
}
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
console.error('TEST_GATEWAY_FAST_ACK_FAIL:', err?.message || err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Regression: OpenLife chat surface must not return canned/automatic replies.
|
|
4
|
+
* The main ask/Gatekeeper path should invoke the configured model executor even
|
|
5
|
+
* for greetings; deterministic status commands remain explicit operational paths.
|
|
6
|
+
*/
|
|
7
|
+
function assertTrue(cond, label) {
|
|
8
|
+
if (!cond)
|
|
9
|
+
throw new Error(`ASSERT_FAILED[${label}]`);
|
|
10
|
+
}
|
|
11
|
+
function installBrainMock(mock) {
|
|
12
|
+
const brainPath = require.resolve('./orchestrator/Brain');
|
|
13
|
+
const orig = require.cache[brainPath];
|
|
14
|
+
require.cache[brainPath] = {
|
|
15
|
+
...orig,
|
|
16
|
+
exports: {
|
|
17
|
+
Brain: class {
|
|
18
|
+
isAnyProviderAvailable() { return true; }
|
|
19
|
+
async thinkFast(_systemPrompt, userMessage) {
|
|
20
|
+
mock.invocations += 1;
|
|
21
|
+
mock.lastUserMessage = userMessage;
|
|
22
|
+
return mock.reply;
|
|
23
|
+
}
|
|
24
|
+
async think(_systemPrompt, userMessage) {
|
|
25
|
+
mock.invocations += 1;
|
|
26
|
+
mock.lastUserMessage = userMessage;
|
|
27
|
+
return mock.reply;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
return () => {
|
|
33
|
+
if (orig)
|
|
34
|
+
require.cache[brainPath] = orig;
|
|
35
|
+
else
|
|
36
|
+
delete require.cache[brainPath];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
process.env.OPENLIFE_OUTCOME_SIMULATION = 'off';
|
|
41
|
+
const mock = { invocations: 0, reply: 'MODEL_REPLY_FROM_GPT55_PATH' };
|
|
42
|
+
const restore = installBrainMock(mock);
|
|
43
|
+
try {
|
|
44
|
+
const { IntentClassifier } = require('./orchestrator/IntentClassifier');
|
|
45
|
+
const { Gatekeeper } = require('./orchestrator/Gatekeeper');
|
|
46
|
+
const classifier = new IntentClassifier();
|
|
47
|
+
const gatekeeper = new Gatekeeper();
|
|
48
|
+
const greeting = 'ola';
|
|
49
|
+
const task = await classifier.classify(greeting);
|
|
50
|
+
const response = await gatekeeper.routeTask(task, greeting, 'no-auto-test');
|
|
51
|
+
assertTrue(response === 'MODEL_REPLY_FROM_GPT55_PATH', 'greeting response comes from Brain/model path');
|
|
52
|
+
assertTrue(mock.invocations === 1, `Brain invoked exactly once for greeting (got ${mock.invocations})`);
|
|
53
|
+
assertTrue(mock.lastUserMessage === greeting, 'original greeting passed to model');
|
|
54
|
+
assertTrue(!/OpenLife online|Como posso ajudar|OPENLIFE ONLINE|Intenção desconhecida/.test(response), 'no canned chat text returned');
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
restore();
|
|
58
|
+
}
|
|
59
|
+
console.log('TEST_NO_AUTOMATIC_MESSAGES_OK');
|
|
60
|
+
}
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
console.error(err);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlife/cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.11",
|
|
4
4
|
"description": "OPEN-LIFE Córtex Orquestrador Dual-Core",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -161,11 +161,14 @@
|
|
|
161
161
|
"test:agent-creator": "npm run build && node dist/test_agent_creator.js",
|
|
162
162
|
"pretest:all": "node scripts/clean-test-pollution.js",
|
|
163
163
|
"test:all": "npm run build && node dist/test_distribution_installability.js && node dist/test_orchestration_assets_lifecycle.js && node dist/test_openlife_runtime_source_truth.js && node dist/test_openlife_evolution_surface.js && node dist/test_openlife_routing_surface.js && node dist/test_openlife_auto_creator_routing.js && node dist/test_openlife_gatekeeper_routing.js && node dist/test_enterprise_agentic_core.js && node dist/test_admin_teams_networks.js && node dist/test_agent_team_skill_network.js && node dist/test_benchmark_engine.js && node dist/test_cli_service_commands.js && node dist/test_consequence_forecaster.js && node dist/test_conversation_memory.js && node dist/test_create_entities.js && node dist/test_designmd_import_registry.js && node dist/test_designmd_mode.js && node dist/test_designmd_mode_workspace.js && node dist/test_dream_organizer.js && node dist/test_dual_mode.js && node dist/test_governance.js && node dist/test_governance_advanced.js && node dist/test_install_flow.js && node dist/test_job_lifecycle.js && node dist/test_memory_orchestrator.js && node dist/test_memory_promotion.js && node dist/test_memory_retention.js && node dist/test_operating_system.js && node dist/test_optimization_loop.js && node dist/test_outcome_simulator.js && node dist/test_performance_scorecard.js && node dist/test_phase6_board.js && node dist/test_phase6_cadence.js && node dist/test_phase6_ops.js && node dist/test_release_gate.js && node dist/test_reversa_contracts_e2e.js && node dist/test_reversa_export_and_strict.js && node dist/test_reversa_full_execution.js && node dist/test_reversa_lite.js && node dist/test_runtime_policy.js && node dist/test_runtime_probe.js && node dist/test_runtime_registry.js && node dist/test_security_download_guard.js && node dist/test_service_command_surface.js && node dist/test_service_completion_policy.js && node dist/test_service_guardrails_delete.js && node dist/test_sources_import_ref.js && node dist/test_sources_scaffold.js && node dist/test_teammate_learning.js && node dist/test_telegram_delete_guardrail.js && node dist/test_daemon_sigterm.js && node dist/test_ask_exit.js && node dist/test_brain_error_diagnostics.js && node dist/test_cli_doc_parity.js && node dist/test_trigger_basic_auth.js && node dist/test_brain_fallback_chain.js && node dist/test_cli_help_surface.js && node dist/test_cli_diagnostics.js && node dist/test_cli_crud_roundtrip.js && node dist/test_subsystems_routing_governance.js && node dist/test_subsystems_org_state.js && node dist/test_subsystems_promotion_memory_assets.js && node dist/test_phase1_check_exit.js && node dist/test_install_flow_host_validation.js && node dist/test_dist_templates_layout.js && node dist/test_host_installer.js && node dist/test_host_uninstaller.js && node dist/test_install_wizard.js && node dist/test_multi_host_docs_parity.js && node dist/test_host_install_e2e.js && node dist/test_runtime_profile_oauth_only.js && node dist/test_atomic_writer.js && node dist/test_mission_checkpoint.js && node dist/test_workflow_parser.js && node dist/test_workflow_engine.js && node dist/test_workflow_e2e.js && node dist/test_distributed_lock.js && node dist/test_watchdog_heartbeat.js && node dist/test_runtime_health_backoff.js && node dist/test_queue_scheduler.js && node dist/test_squad_skill_creator.js && node dist/test_aiobuilder_cli_parity.js && node dist/test_catalog_quality.js && node dist/test_royal_stack_golden.js && node dist/test_capability_pack_schema.js && node dist/test_capability_genesis_engine.js && node dist/test_workflow_schema_backward_compat.js && node dist/test_service_mode_explicit_only.js && node dist/test_deep_research_capability.js && node dist/test_guided_creator_cli.js && node dist/test_governance_v13_policies.js && node dist/test_gateway_telegram_guardrails.js && node dist/test_cron_manager.js && node dist/test_profile_toolset_mcp.js && node dist/test_squad_skill_design_llm.js && node dist/test_workflow_condition_parser.js && node dist/test_security_download_and_scan.js && node dist/test_host_installers_gemini_codex.js && node dist/test_toolset_enforcement.js && node dist/test_creator_placeholders_completed.js && node dist/test_performance_latency.js && node dist/test_post_mission_evaluation.js && node dist/test_governance_scope_ledger.js && node dist/test_consequence_forecast_brain.js && node dist/test_remote_publish.js && node dist/test_process_sandbox.js && node dist/test_v15_e2e_integration.js && node dist/test_doctor_sandbox_check.js && node dist/test_task_executor_sandbox_optin.js && node dist/test_forecast_brain_wiring.js && node dist/test_status_command.js && node dist/test_logs_command.js && node dist/test_agent_creator.js",
|
|
164
|
-
"prepublishOnly": "npm run test:all"
|
|
164
|
+
"prepublishOnly": "npm run test:all",
|
|
165
|
+
"test:no-automatic-messages": "npm run build && node dist/test_no_automatic_messages.js",
|
|
166
|
+
"test:gateway-fast-ack": "npm run build && node dist/test_gateway_fast_ack.js"
|
|
165
167
|
},
|
|
166
168
|
"dependencies": {
|
|
167
169
|
"@anthropic-ai/sdk": "^0.86.1",
|
|
168
170
|
"@google/generative-ai": "^0.24.1",
|
|
171
|
+
"@openai/codex": "^0.130.0",
|
|
169
172
|
"axios": "^1.15.0",
|
|
170
173
|
"commander": "^11.1.0",
|
|
171
174
|
"dotenv": "^16.3.1",
|