@openlife/cli 1.7.14 → 1.8.2
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/INSTALL.md +29 -1
- package/dist/cli/ChatTui.js +32 -0
- package/dist/cli/InstallModules.js +17 -2
- package/dist/cli/InstallWizardV2.js +110 -0
- package/dist/cli/install/Multiselect.js +285 -0
- package/dist/cli/install/OAuthRunner.js +170 -0
- package/dist/cli/install/Phases.js +864 -0
- package/dist/cli/install/ProvidersCatalog.js +320 -0
- package/dist/cli/install/WizardIO.js +271 -0
- package/dist/cli/install/types.js +17 -0
- package/dist/index.js +72 -33
- package/dist/orchestrator/ConsequenceForecaster.js +24 -1
- package/dist/orchestrator/DreamGoalStore.js +130 -0
- package/dist/orchestrator/Gatekeeper.js +14 -0
- package/dist/orchestrator/Gateway.js +149 -10
- package/dist/orchestrator/ModelManager.js +7 -1
- package/dist/orchestrator/OrchestrationLoop.js +12 -0
- package/dist/orchestrator/ParallelOrchestrationLoop.js +12 -2
- package/dist/orchestrator/RuntimePolicy.js +4 -1
- package/dist/orchestrator/ServiceCompletionPolicy.js +15 -0
- package/dist/orchestrator/SynthesizerAgent.js +20 -1
- package/dist/orchestrator/TaskExecutor.js +53 -0
- package/dist/orchestrator/capability/CapabilityGenesisEngine.js +66 -11
- package/dist/test_capability_genesis_engine.js +1 -1
- package/dist/test_chat_smoke_command.js +59 -0
- package/dist/test_dream_goal_commands.js +76 -0
- package/dist/test_gateway_telegram_formatting.js +74 -0
- package/dist/test_install_wizard_v2.js +193 -0
- package/dist/test_on_demand_voice_reply.js +65 -0
- package/dist/test_remaining_sprints_contracts.js +103 -0
- package/dist/test_runtime_policy.js +25 -6
- package/dist/test_service_completion_policy.js +7 -0
- package/dist/test_subsystems_routing_governance.js +13 -3
- package/dist/test_task_executor_gemini_api.js +68 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ const SystemDoctor_1 = require("./orchestrator/SystemDoctor");
|
|
|
76
76
|
const WorldClassCommands_1 = require("./cli/WorldClassCommands");
|
|
77
77
|
const InstallBanner_1 = require("./cli/InstallBanner");
|
|
78
78
|
const DreamOrganizer_1 = require("./cli/DreamOrganizer");
|
|
79
|
+
const DreamGoalStore_1 = require("./orchestrator/DreamGoalStore");
|
|
79
80
|
const InstallFlow_1 = require("./cli/InstallFlow");
|
|
80
81
|
const InstallWizard_1 = require("./cli/InstallWizard");
|
|
81
82
|
const InstallHeadless_1 = require("./cli/InstallHeadless");
|
|
@@ -236,6 +237,22 @@ program
|
|
|
236
237
|
.name('openlife')
|
|
237
238
|
.description('OPEN-LIFE Córtex Orquestrador (Dual-Core)')
|
|
238
239
|
.version(pkgVersion);
|
|
240
|
+
program
|
|
241
|
+
.command('dream [text...]')
|
|
242
|
+
.description('Define ou consulta a visão /dream atual do OpenLife')
|
|
243
|
+
.action((parts) => {
|
|
244
|
+
const store = new DreamGoalStore_1.DreamGoalStore();
|
|
245
|
+
const input = `/dream ${Array.isArray(parts) ? parts.join(' ') : ''}`.trim();
|
|
246
|
+
console.log(store.handleSlashCommand(input));
|
|
247
|
+
});
|
|
248
|
+
program
|
|
249
|
+
.command('goal [text...]')
|
|
250
|
+
.description('Define ou consulta a missão /goal atual do OpenLife')
|
|
251
|
+
.action((parts) => {
|
|
252
|
+
const store = new DreamGoalStore_1.DreamGoalStore();
|
|
253
|
+
const input = `/goal ${Array.isArray(parts) ? parts.join(' ') : ''}`.trim();
|
|
254
|
+
console.log(store.handleSlashCommand(input));
|
|
255
|
+
});
|
|
239
256
|
program
|
|
240
257
|
.command('install')
|
|
241
258
|
.description('Onboarding unificado: instala CLI ou Agente Autônomo')
|
|
@@ -296,46 +313,68 @@ program
|
|
|
296
313
|
// INIT — Interactive install wizard (Story 3.5)
|
|
297
314
|
// ============================================================================
|
|
298
315
|
program.command('init')
|
|
299
|
-
.description('Interactive install wizard —
|
|
300
|
-
.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
.description('Interactive install wizard — phase-based, sequential, multi-host (v2)')
|
|
317
|
+
.option('--legacy', 'use the v1 (batched-Q&A) wizard instead of v2 (sequential phases)')
|
|
318
|
+
.action(async (options) => {
|
|
319
|
+
if (options.legacy) {
|
|
320
|
+
// v1 wizard — kept for users who scripted around the old prompts.
|
|
321
|
+
console.log((0, InstallBanner_1.installationBanner)());
|
|
322
|
+
console.log('');
|
|
323
|
+
const { InstallWizard, ReadlineAnswerProvider } = require('./cli/InstallWizard');
|
|
324
|
+
const { InstallFlow } = require('./cli/InstallFlow');
|
|
325
|
+
const provider = new ReadlineAnswerProvider();
|
|
326
|
+
const wizard = new InstallWizard(process.cwd(), provider);
|
|
327
|
+
try {
|
|
328
|
+
const result = await wizard.run();
|
|
329
|
+
if (!result.ok) {
|
|
330
|
+
console.log(JSON.stringify(result, null, 2));
|
|
331
|
+
process.exitCode = result.reason === 'user_aborted' ? 0 : 1;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const flow = new InstallFlow();
|
|
335
|
+
const installResult = flow.run(result.options);
|
|
336
|
+
for (const line of flow.renderSummary(installResult))
|
|
337
|
+
console.log(line);
|
|
338
|
+
if (result.warnings?.length) {
|
|
339
|
+
console.log('');
|
|
340
|
+
console.log('⚠️ Warnings:');
|
|
341
|
+
for (const w of result.warnings)
|
|
342
|
+
console.log(` - ${w}`);
|
|
343
|
+
}
|
|
320
344
|
console.log('');
|
|
321
|
-
console.log('
|
|
322
|
-
for (const w of result.warnings)
|
|
323
|
-
console.log(` - ${w}`);
|
|
345
|
+
console.log('✅ OpenLife ready (v1 wizard).');
|
|
324
346
|
}
|
|
347
|
+
finally {
|
|
348
|
+
provider.close?.();
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// v2 wizard — phase-based, sequential, multi-host, multi-product.
|
|
353
|
+
// Banner is rendered by the first phase, not here.
|
|
354
|
+
const { InstallWizardV2 } = require('./cli/InstallWizardV2');
|
|
355
|
+
const wizard = new InstallWizardV2();
|
|
356
|
+
const result = await wizard.run();
|
|
357
|
+
if (!result.ok) {
|
|
325
358
|
console.log('');
|
|
326
|
-
console.log(
|
|
327
|
-
|
|
328
|
-
|
|
359
|
+
console.log(`Install wizard ended without finishing (${result.reason}${result.detail ? `: ${result.detail}` : ''}).`);
|
|
360
|
+
process.exitCode = result.reason === 'user_aborted' ? 0 : 1;
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
console.log('');
|
|
364
|
+
console.log('✅ OpenLife ready.');
|
|
365
|
+
console.log('');
|
|
366
|
+
console.log('Try:');
|
|
367
|
+
if (result.context.products.includes('openlife-core')) {
|
|
329
368
|
console.log(' openlife ask "hello, what can you do?"');
|
|
330
369
|
console.log(' openlife status');
|
|
331
|
-
console.log(' openlife --help');
|
|
332
|
-
console.log('');
|
|
333
|
-
console.log('Docs: https://github.com/GOOODZ/openlife-core');
|
|
334
370
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
371
|
+
if (result.context.products.includes('openlife-agent')) {
|
|
372
|
+
console.log(' openlife agent start --daemon');
|
|
373
|
+
console.log(' openlife agent status');
|
|
338
374
|
}
|
|
375
|
+
console.log(' openlife --help');
|
|
376
|
+
console.log('');
|
|
377
|
+
console.log('Docs: https://github.com/GOOODZ/openlife-core');
|
|
339
378
|
});
|
|
340
379
|
// ============================================================================
|
|
341
380
|
// OBSERVABILITY — `openlife status`, `openlife logs`
|
|
@@ -48,7 +48,9 @@ class ConsequenceForecaster {
|
|
|
48
48
|
];
|
|
49
49
|
const selectedPath = this.pickBest(paths);
|
|
50
50
|
const executiveSummary = this.buildExecutiveSummary(selectedPath, governanceRisk, normalizedGoal);
|
|
51
|
-
|
|
51
|
+
const valueScores = this.scoreValues(selectedPath, governanceRisk, normalizedGoal);
|
|
52
|
+
const horizons = this.buildHorizons(selectedPath, governanceRisk, normalizedGoal);
|
|
53
|
+
return { selectedPath, consideredPaths: paths, executiveSummary, valueScores, horizons };
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Story 14.3 (v1.5) — Brain-enriched forecast with persistent cache.
|
|
@@ -257,6 +259,27 @@ class ConsequenceForecaster {
|
|
|
257
259
|
return 'safety';
|
|
258
260
|
return 'neutral';
|
|
259
261
|
}
|
|
262
|
+
scoreValues(selectedPath, risk, normalizedGoal) {
|
|
263
|
+
const safety = risk === 'high' && selectedPath.id === 'conservative' ? 9 : risk === 'high' ? 5 : 7;
|
|
264
|
+
const usefulness = selectedPath.id === 'aggressive' ? 8 : selectedPath.id === 'balanced' ? 8 : 6;
|
|
265
|
+
const reversibility = normalizedGoal.includes('delete') || normalizedGoal.includes('remove') || normalizedGoal.includes('produção') || normalizedGoal.includes('production') ? 5 : 8;
|
|
266
|
+
const cost = selectedPath.policy.maxBranches > 2 ? 5 : 8;
|
|
267
|
+
return [
|
|
268
|
+
{ criterion: 'safety', score: safety, rationale: 'Prioriza risco governado e superfície de execução.' },
|
|
269
|
+
{ criterion: 'usefulness', score: usefulness, rationale: 'Estima probabilidade de entregar valor útil ao usuário.' },
|
|
270
|
+
{ criterion: 'reversibility', score: reversibility, rationale: 'Avalia facilidade de desfazer ou pausar a ação.' },
|
|
271
|
+
{ criterion: 'cost', score: cost, rationale: 'Penaliza fan-out e paralelismo quando aumentam custo operacional.' },
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
buildHorizons(selectedPath, risk, normalizedGoal) {
|
|
275
|
+
const safeAction = risk === 'high' ? 'exigir validação explícita antes de efeitos externos' : 'executar com validação padrão';
|
|
276
|
+
return {
|
|
277
|
+
now: { impact: `Rota ${selectedPath.id} selecionada; ${selectedPath.consequences[0]}.`, recommendedAction: safeAction, risk },
|
|
278
|
+
'24h': { impact: 'Evidências e logs devem confirmar ausência de regressão operacional.', recommendedAction: 'revisar serviceEvidence e eventos de governança', risk: risk === 'low' ? 'low' : 'medium' },
|
|
279
|
+
'7d': { impact: 'Padrões de falha ou retrabalho começam a aparecer nos KPIs do serviço.', recommendedAction: 'avaliar scorecard e ajustar capability/workflow se necessário', risk: 'medium' },
|
|
280
|
+
'30d': { impact: 'Efeito acumulado em custo, confiabilidade e confiança do operador fica mensurável.', recommendedAction: 'promover, pausar ou refatorar a capacidade com base em evidência', risk: normalizedGoal.includes('production') || normalizedGoal.includes('produção') ? 'high' : 'medium' },
|
|
281
|
+
};
|
|
282
|
+
}
|
|
260
283
|
scorePath(risk, policy, executors, normalizedGoal, conservative) {
|
|
261
284
|
let score = 0;
|
|
262
285
|
if (executors.length > 1)
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
exports.DreamGoalStore = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class DreamGoalStore {
|
|
40
|
+
stateDir;
|
|
41
|
+
constructor(stateDir = process.env.OPENLIFE_STATE_DIR || path.join(process.cwd(), '.openlife')) {
|
|
42
|
+
this.stateDir = stateDir;
|
|
43
|
+
}
|
|
44
|
+
setDream(value) {
|
|
45
|
+
return this.set('dream', value);
|
|
46
|
+
}
|
|
47
|
+
setGoal(value) {
|
|
48
|
+
return this.set('goal', value);
|
|
49
|
+
}
|
|
50
|
+
getDream() {
|
|
51
|
+
return this.load().dream;
|
|
52
|
+
}
|
|
53
|
+
getGoal() {
|
|
54
|
+
return this.load().goal;
|
|
55
|
+
}
|
|
56
|
+
summary() {
|
|
57
|
+
const state = this.load();
|
|
58
|
+
const dream = state.dream?.value || 'não definido';
|
|
59
|
+
const goal = state.goal?.value || 'não definido';
|
|
60
|
+
return [`Dream atual: ${dream}`, `Goal atual: ${goal}`].join('\n');
|
|
61
|
+
}
|
|
62
|
+
handleSlashCommand(input) {
|
|
63
|
+
const trimmed = input.trim();
|
|
64
|
+
const match = trimmed.match(/^\/(dream|goal)(?:\s+([\s\S]+))?$/i);
|
|
65
|
+
if (!match)
|
|
66
|
+
return null;
|
|
67
|
+
const kind = match[1].toLowerCase();
|
|
68
|
+
const value = (match[2] || '').trim();
|
|
69
|
+
if (!value) {
|
|
70
|
+
const current = kind === 'dream' ? this.getDream() : this.getGoal();
|
|
71
|
+
if (!current) {
|
|
72
|
+
return `${this.label(kind)} ainda não definido. Use /${kind} <texto> para registrar.`;
|
|
73
|
+
}
|
|
74
|
+
return `${this.label(kind)} atual:\n${current.value}\n\nAtualizado em: ${current.updatedAt}`;
|
|
75
|
+
}
|
|
76
|
+
const entry = this.set(kind, value);
|
|
77
|
+
const counterpart = kind === 'dream' ? this.getGoal() : this.getDream();
|
|
78
|
+
const nextHint = kind === 'dream'
|
|
79
|
+
? 'Use /goal para transformar esse sonho em uma missão executável.'
|
|
80
|
+
: 'Use /dream para ajustar a visão maior por trás deste objetivo.';
|
|
81
|
+
return [
|
|
82
|
+
`${this.label(kind)} registrado ✅`,
|
|
83
|
+
'',
|
|
84
|
+
entry.value,
|
|
85
|
+
'',
|
|
86
|
+
counterpart ? this.summary() : nextHint,
|
|
87
|
+
].join('\n');
|
|
88
|
+
}
|
|
89
|
+
set(kind, rawValue) {
|
|
90
|
+
const value = rawValue.trim();
|
|
91
|
+
if (!value)
|
|
92
|
+
throw new Error(`${kind}_cannot_be_empty`);
|
|
93
|
+
const state = this.load();
|
|
94
|
+
const entry = { kind, value, updatedAt: new Date().toISOString() };
|
|
95
|
+
state[kind] = entry;
|
|
96
|
+
state.history = [...(state.history || []), entry].slice(-50);
|
|
97
|
+
this.save(state);
|
|
98
|
+
this.appendMarkdown(entry);
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
label(kind) {
|
|
102
|
+
return kind === 'dream' ? 'Dream' : 'Goal';
|
|
103
|
+
}
|
|
104
|
+
filePath() {
|
|
105
|
+
return path.join(this.stateDir, 'dream-goal.json');
|
|
106
|
+
}
|
|
107
|
+
load() {
|
|
108
|
+
const file = this.filePath();
|
|
109
|
+
if (!fs.existsSync(file))
|
|
110
|
+
return { history: [] };
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
113
|
+
return { ...parsed, history: parsed.history || [] };
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { history: [] };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
save(state) {
|
|
120
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
121
|
+
fs.writeFileSync(this.filePath(), JSON.stringify(state, null, 2), 'utf-8');
|
|
122
|
+
}
|
|
123
|
+
appendMarkdown(entry) {
|
|
124
|
+
const dir = path.join(this.stateDir, 'dream-goal');
|
|
125
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
126
|
+
const file = path.join(dir, `${entry.kind}.md`);
|
|
127
|
+
fs.appendFileSync(file, `## /${entry.kind} ${entry.updatedAt}\n\n${entry.value}\n\n`, 'utf-8');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.DreamGoalStore = DreamGoalStore;
|
|
@@ -53,6 +53,7 @@ const ModelManager_1 = require("./ModelManager");
|
|
|
53
53
|
const WorldClassCommands_1 = require("../cli/WorldClassCommands");
|
|
54
54
|
const GovernanceLayer_1 = require("./GovernanceLayer");
|
|
55
55
|
const child_process_1 = require("child_process");
|
|
56
|
+
const DreamGoalStore_1 = require("./DreamGoalStore");
|
|
56
57
|
class Gatekeeper {
|
|
57
58
|
memory;
|
|
58
59
|
squadManager;
|
|
@@ -117,6 +118,12 @@ class Gatekeeper {
|
|
|
117
118
|
}
|
|
118
119
|
async routeTask(task, userInput, userId = "default", options) {
|
|
119
120
|
console.log(`\n[GATEKEEPER] Intenção: ${task.intent} | Budget: $${task.budgetLimit} | User: ${userId}`);
|
|
121
|
+
const dreamGoalResponse = new DreamGoalStore_1.DreamGoalStore().handleSlashCommand(userInput);
|
|
122
|
+
if (dreamGoalResponse) {
|
|
123
|
+
this.sessionManager.addMessage(userId, 'user', userInput);
|
|
124
|
+
this.sessionManager.addMessage(userId, 'agent', dreamGoalResponse);
|
|
125
|
+
return dreamGoalResponse;
|
|
126
|
+
}
|
|
120
127
|
const parsedCommand = this.commandLanguage.parse(userInput);
|
|
121
128
|
const commandResult = this.commandRouter.route(parsedCommand);
|
|
122
129
|
if (commandResult) {
|
|
@@ -280,6 +287,9 @@ ${searchResult ? `Contexto relevante:\n${searchResult.substring(0, 900)}\n` : ''
|
|
|
280
287
|
Histórico recente:
|
|
281
288
|
${recentHistory.slice(-1200)}
|
|
282
289
|
|
|
290
|
+
Dream/Goal atual do usuário:
|
|
291
|
+
${new DreamGoalStore_1.DreamGoalStore().summary()}
|
|
292
|
+
|
|
283
293
|
Instruções:
|
|
284
294
|
- Responda diretamente à última mensagem, em português natural.
|
|
285
295
|
- Seja rápido e conciso por padrão.
|
|
@@ -319,6 +329,10 @@ Instruções:
|
|
|
319
329
|
const usedFallback = state.attempts.some(a => a.role === 'executor' && a.status === 'partial');
|
|
320
330
|
console.log("[SELF-EVALUATION LOOP] Analisando o resultado real do loop multiagente...");
|
|
321
331
|
const traceMode = String(process.env.OPENLIFE_REASONING_MODE || 'errors').toLowerCase();
|
|
332
|
+
const isResearchTask = task.intent === IntentClassifier_1.TaskIntent.RESEARCH_ANALYSIS && inferredMode === 'task';
|
|
333
|
+
if (isResearchTask) {
|
|
334
|
+
return orchestration.response;
|
|
335
|
+
}
|
|
322
336
|
const toolTrace = state.attempts.slice(-10).map((a) => {
|
|
323
337
|
const raw = String(a.summary || '').replace(/\n/g, ' ');
|
|
324
338
|
const m = raw.match(/^TOOL_TRACE\s+(search_files|read_file|write_file|terminal):\s*(.*)$/i);
|
|
@@ -41,6 +41,7 @@ const telegraf_1 = require("telegraf");
|
|
|
41
41
|
const filters_1 = require("telegraf/filters");
|
|
42
42
|
const IntentClassifier_1 = require("./IntentClassifier");
|
|
43
43
|
const Gatekeeper_1 = require("./Gatekeeper");
|
|
44
|
+
const DreamGoalStore_1 = require("./DreamGoalStore");
|
|
44
45
|
const VoiceManager_1 = require("./VoiceManager");
|
|
45
46
|
const axios_1 = __importDefault(require("axios"));
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
@@ -91,6 +92,21 @@ class Gateway {
|
|
|
91
92
|
static redactBotToken(text) {
|
|
92
93
|
return String(text).replace(/(\d{6,12}):[A-Za-z0-9_-]{20,}/g, '$1:[REDACTED]');
|
|
93
94
|
}
|
|
95
|
+
static classifyTelegramLaunchError(err) {
|
|
96
|
+
const raw = err instanceof Error ? err.message : String(err || '');
|
|
97
|
+
const detail = Gateway.redactBotToken(raw).slice(0, 500);
|
|
98
|
+
if (raw.includes('409') || raw.includes('terminated by other getUpdates request') || raw.toLowerCase().includes('conflict')) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: 'telegram_polling_conflict',
|
|
102
|
+
recoveryOptions: ['stop_conflicting_instance', 'replace_token', 'skip_telegram_for_now'],
|
|
103
|
+
detail,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!raw)
|
|
107
|
+
return { ok: true };
|
|
108
|
+
return { ok: false, error: 'telegram_launch_failed', recoveryOptions: ['check_token', 'switch_to_webhook', 'skip_telegram_for_now'], detail };
|
|
109
|
+
}
|
|
94
110
|
/**
|
|
95
111
|
* Validate the configured TELEGRAM_BOT_TOKEN by calling getMe.
|
|
96
112
|
* Returns `{ ok: true, botId, botUsername }` on success;
|
|
@@ -359,7 +375,11 @@ class Gateway {
|
|
|
359
375
|
// Register webhook handler on the existing Express app — no separate
|
|
360
376
|
// HTTP server, no port conflict. Telegraf's webhookCallback returns
|
|
361
377
|
// an Express-compatible middleware that parses updates.
|
|
362
|
-
|
|
378
|
+
// Important: do not mount the callback at `webhookPath` while also
|
|
379
|
+
// passing `webhookPath` into Telegraf. Express strips the mount
|
|
380
|
+
// prefix before the middleware sees `req.url`, so Telegraf would
|
|
381
|
+
// see `/` and skip the update, causing Telegram to receive 404.
|
|
382
|
+
this.app.use(this.bot.webhookCallback(webhookPath));
|
|
363
383
|
const rawDomain = process.env.OPENLIFE_WEBHOOK_DOMAIN
|
|
364
384
|
|| process.env.RAILWAY_PUBLIC_DOMAIN
|
|
365
385
|
|| process.env.RAILWAY_STATIC_URL
|
|
@@ -384,9 +404,9 @@ class Gateway {
|
|
|
384
404
|
// Telegram rejects getUpdates with 409. Best-effort, non-fatal.
|
|
385
405
|
this.bot.telegram.deleteWebhook({ drop_pending_updates: false }).catch(() => { });
|
|
386
406
|
this.bot.launch().catch((err) => {
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
389
|
-
console.error(
|
|
407
|
+
const classified = Gateway.classifyTelegramLaunchError(err);
|
|
408
|
+
if ('error' in classified && classified.error === 'telegram_polling_conflict') {
|
|
409
|
+
console.error(`[GATEWAY] Telegram 409 Conflict: outra instância está conectada com este bot. Recovery: ${classified.recoveryOptions.join(' | ')}. Detail: ${classified.detail}`);
|
|
390
410
|
return;
|
|
391
411
|
}
|
|
392
412
|
console.error('[GATEWAY] Falha ao iniciar Telegram bot:', err);
|
|
@@ -583,12 +603,131 @@ class Gateway {
|
|
|
583
603
|
return true;
|
|
584
604
|
return userId === this.allowedTelegramUserId;
|
|
585
605
|
}
|
|
606
|
+
shouldRespondByVoice(text) {
|
|
607
|
+
const normalized = text.toLowerCase().trim();
|
|
608
|
+
if (/^\/voice\b|^\/voz\b/.test(normalized))
|
|
609
|
+
return true;
|
|
610
|
+
return /\b(responda|responder|manda|mande|envia|envie|fale|fala)\b[\s\S]{0,80}\b(por|em)\s+(voz|áudio|audio)\b/i.test(normalized)
|
|
611
|
+
|| /\bquero\s+(ouvir|áudio|audio|voz)\b/i.test(normalized);
|
|
612
|
+
}
|
|
613
|
+
stripVoiceDirective(text) {
|
|
614
|
+
let cleaned = text.replace(/^\/(voice|voz)\s*/i, '').trim();
|
|
615
|
+
cleaned = cleaned
|
|
616
|
+
.replace(/\b(responda|responder|manda|mande|envia|envie)\s+(por|em)\s+(voz|áudio|audio)\b[:,\s-]*/ig, '')
|
|
617
|
+
.replace(/\b(por favor,?\s*)?(responda|responder)\s+(isso\s+)?(por|em)\s+(voz|áudio|audio)\b/ig, '')
|
|
618
|
+
.trim();
|
|
619
|
+
return cleaned || text;
|
|
620
|
+
}
|
|
621
|
+
voiceUnavailableMessage(err) {
|
|
622
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
623
|
+
const safeDetail = this.governance.redact(detail).slice(0, 500);
|
|
624
|
+
return [
|
|
625
|
+
'Não consegui gerar a voz neste momento, então deixei em texto.',
|
|
626
|
+
`Motivo técnico: ${safeDetail}`,
|
|
627
|
+
'Para ativar voz sob demanda, configure um provider TTS no Railway: OPENAI_API_KEY ou ELEVENLABS_API_KEY.'
|
|
628
|
+
].join('\n');
|
|
629
|
+
}
|
|
630
|
+
isMarkdownTableSeparator(line) {
|
|
631
|
+
return /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
|
632
|
+
}
|
|
633
|
+
splitMarkdownTableRow(line) {
|
|
634
|
+
let stripped = line.trim();
|
|
635
|
+
if (stripped.startsWith('|'))
|
|
636
|
+
stripped = stripped.slice(1);
|
|
637
|
+
if (stripped.endsWith('|'))
|
|
638
|
+
stripped = stripped.slice(0, -1);
|
|
639
|
+
return stripped.split('|').map(cell => this.stripInlineMarkdown(cell.trim()));
|
|
640
|
+
}
|
|
641
|
+
renderMarkdownTableForTelegram(tableBlock) {
|
|
642
|
+
if (tableBlock.length < 3)
|
|
643
|
+
return tableBlock.join('\n');
|
|
644
|
+
const headers = this.splitMarkdownTableRow(tableBlock[0]);
|
|
645
|
+
if (headers.length < 2)
|
|
646
|
+
return tableBlock.join('\n');
|
|
647
|
+
const rows = [];
|
|
648
|
+
for (const row of tableBlock.slice(2)) {
|
|
649
|
+
const cells = this.splitMarkdownTableRow(row);
|
|
650
|
+
if (!cells.length)
|
|
651
|
+
continue;
|
|
652
|
+
const heading = cells[0] || 'Item';
|
|
653
|
+
const values = cells.slice(1);
|
|
654
|
+
rows.push(`• ${heading}`);
|
|
655
|
+
for (let i = 1; i < headers.length; i += 1) {
|
|
656
|
+
const label = headers[i];
|
|
657
|
+
const value = values[i - 1] || '';
|
|
658
|
+
if (label && value)
|
|
659
|
+
rows.push(` ${label}: ${value}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return rows.join('\n');
|
|
663
|
+
}
|
|
664
|
+
rewriteMarkdownTablesForTelegram(text) {
|
|
665
|
+
if (!text.includes('|'))
|
|
666
|
+
return text;
|
|
667
|
+
const lines = text.split('\n');
|
|
668
|
+
const output = [];
|
|
669
|
+
let i = 0;
|
|
670
|
+
let inFence = false;
|
|
671
|
+
while (i < lines.length) {
|
|
672
|
+
const line = lines[i];
|
|
673
|
+
if (line.trimStart().startsWith('```')) {
|
|
674
|
+
inFence = !inFence;
|
|
675
|
+
output.push(line);
|
|
676
|
+
i += 1;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (!inFence && line.includes('|') && i + 1 < lines.length && this.isMarkdownTableSeparator(lines[i + 1])) {
|
|
680
|
+
const tableBlock = [line, lines[i + 1]];
|
|
681
|
+
let j = i + 2;
|
|
682
|
+
while (j < lines.length && lines[j].trim() !== '' && lines[j].includes('|')) {
|
|
683
|
+
tableBlock.push(lines[j]);
|
|
684
|
+
j += 1;
|
|
685
|
+
}
|
|
686
|
+
output.push(this.renderMarkdownTableForTelegram(tableBlock));
|
|
687
|
+
i = j;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
output.push(line);
|
|
691
|
+
i += 1;
|
|
692
|
+
}
|
|
693
|
+
return output.join('\n');
|
|
694
|
+
}
|
|
695
|
+
stripInlineMarkdown(text) {
|
|
696
|
+
return text
|
|
697
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
698
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
699
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
700
|
+
.replace(/~~([^~]+)~~/g, '$1')
|
|
701
|
+
.replace(/\*([^*\n]+)\*/g, '$1')
|
|
702
|
+
.replace(/(?<!\w)_([^_\n]+)_(?!\w)/g, '$1');
|
|
703
|
+
}
|
|
704
|
+
formatForTelegramPlainText(text) {
|
|
705
|
+
const withoutTables = this.rewriteMarkdownTablesForTelegram(text);
|
|
706
|
+
return withoutTables
|
|
707
|
+
.split('\n')
|
|
708
|
+
.map(line => line
|
|
709
|
+
.replace(/^\s*#{1,6}\s+/, '')
|
|
710
|
+
.replace(/^\s*>\s?/, ''))
|
|
711
|
+
.filter(line => !/^\s*---+\s*$/.test(line))
|
|
712
|
+
.map(line => this.stripInlineMarkdown(line))
|
|
713
|
+
.join('\n')
|
|
714
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
715
|
+
.trim();
|
|
716
|
+
}
|
|
586
717
|
async processInput(ctx, userId, text) {
|
|
587
718
|
if (!this.isAuthorizedUser(userId)) {
|
|
588
719
|
console.warn(`[GATEWAY] Acesso negado para userId=${userId}`);
|
|
589
720
|
await this.safeReply(ctx, 'Acesso negado por policy de segurança deste bot.');
|
|
590
721
|
return;
|
|
591
722
|
}
|
|
723
|
+
const wantsVoiceReply = this.shouldRespondByVoice(text);
|
|
724
|
+
const effectiveText = wantsVoiceReply ? this.stripVoiceDirective(text) : text;
|
|
725
|
+
const dreamGoalStore = new DreamGoalStore_1.DreamGoalStore();
|
|
726
|
+
const dreamGoalResponse = dreamGoalStore.handleSlashCommand(effectiveText);
|
|
727
|
+
if (dreamGoalResponse) {
|
|
728
|
+
await this.safeReply(ctx, dreamGoalResponse);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
592
731
|
// Fast ACK pattern: fire a placeholder reply in parallel with the actual
|
|
593
732
|
// classifier+gatekeeper work. When the work completes, edit the ACK with
|
|
594
733
|
// the real answer (Telegraf editMessageText). Falls back to a fresh
|
|
@@ -628,21 +767,21 @@ class Gateway {
|
|
|
628
767
|
const stopTyping = this.startTypingIndicator(ctx);
|
|
629
768
|
const trace = [];
|
|
630
769
|
try {
|
|
631
|
-
trace.push(`🔎 classify_intent: "${
|
|
632
|
-
const task = await this.classifier.classify(
|
|
770
|
+
trace.push(`🔎 classify_intent: "${effectiveText.slice(0, 80).replace(/\n/g, ' ')}${effectiveText.length > 80 ? '...' : ''}"`);
|
|
771
|
+
const task = await this.classifier.classify(effectiveText);
|
|
633
772
|
trace.push(`🧭 route_task: "${String(task.intent)}"`);
|
|
634
|
-
const response = await this.gatekeeper.routeTask(task,
|
|
773
|
+
const response = await this.gatekeeper.routeTask(task, effectiveText, userId);
|
|
635
774
|
trace.push(`✅ finalize_reply: "${String(task.intent)}"`);
|
|
636
|
-
const safeResponse = this.governance.redact(response);
|
|
775
|
+
const safeResponse = this.formatForTelegramPlainText(this.governance.redact(response));
|
|
637
776
|
const finalResponse = this.reasoningMode === 'always' ? `${trace.join('\n')}\n\n${safeResponse}` : safeResponse;
|
|
638
|
-
if (this.ttsEnabled && typeof ctx.sendVoice === 'function') {
|
|
777
|
+
if ((this.ttsEnabled || wantsVoiceReply) && typeof ctx.sendVoice === 'function') {
|
|
639
778
|
try {
|
|
640
779
|
const audioPath = await this.voiceManager.generateSpeech(finalResponse);
|
|
641
780
|
await ctx.sendVoice({ source: audioPath }, { caption: finalResponse });
|
|
642
781
|
}
|
|
643
782
|
catch (ttsError) {
|
|
644
783
|
console.log("[GATEWAY] Fallback para texto (Erro no TTS).", ttsError);
|
|
645
|
-
await sendFinal(finalResponse);
|
|
784
|
+
await sendFinal(wantsVoiceReply ? `${finalResponse}\n\n${this.voiceUnavailableMessage(ttsError)}` : finalResponse);
|
|
646
785
|
}
|
|
647
786
|
}
|
|
648
787
|
else {
|
|
@@ -73,7 +73,13 @@ class ModelManager {
|
|
|
73
73
|
const parts = input.split('/');
|
|
74
74
|
const providerStr = parts[0];
|
|
75
75
|
const name = parts.slice(1).join('/');
|
|
76
|
-
const validProviders = [
|
|
76
|
+
const validProviders = [
|
|
77
|
+
'openai-api', 'openai-cli', 'anthropic', 'gemini-api', 'gemini-cli', 'ollama', 'openrouter',
|
|
78
|
+
'xai', 'deepseek', 'groq', 'together', 'mistral',
|
|
79
|
+
'bedrock', 'qwen', 'kimi', 'minimax', 'glm', 'huggingface', 'nvidia-nim', 'perplexity',
|
|
80
|
+
'vllm', 'sglang', 'llamacpp', 'lmstudio',
|
|
81
|
+
'litellm', 'clawrouter', 'custom-openai',
|
|
82
|
+
];
|
|
77
83
|
const provider = providerStr.toLowerCase();
|
|
78
84
|
if (!validProviders.includes(provider)) {
|
|
79
85
|
throw new Error(`Provedor não suportado: '${provider}'. Provedores válidos: ${validProviders.join(', ')}.`);
|
|
@@ -468,6 +468,7 @@ class OrchestrationLoop {
|
|
|
468
468
|
if ((route.mode === 'parallel' || executionPolicy.swarmMode === 'consensus') && task.intent === IntentClassifier_1.TaskIntent.RESEARCH_ANALYSIS) {
|
|
469
469
|
const parallelResult = await this.parallelLoop.runResearchBranches(planning.normalizedGoal, `orchestration_${taskId}`, executionPolicy.maxBranches);
|
|
470
470
|
executorOutput += `\n\n[Parallel Arbitration]\n${parallelResult.arbitration.synthesis || parallelResult.branches.join('\n\n---\n\n')}`;
|
|
471
|
+
state.artifacts.push(...parallelResult.artifactPaths);
|
|
471
472
|
finalStatus = 'partial';
|
|
472
473
|
state.attempts.push({
|
|
473
474
|
role: 'executor',
|
|
@@ -587,6 +588,17 @@ class OrchestrationLoop {
|
|
|
587
588
|
at: new Date().toISOString()
|
|
588
589
|
});
|
|
589
590
|
state.memoryCandidates = await this.memoryCurator.curate(state);
|
|
591
|
+
if (!state.serviceEvidence || state.serviceEvidence.length === 0) {
|
|
592
|
+
const now = new Date().toISOString();
|
|
593
|
+
const primaryArtifact = state.artifacts[0];
|
|
594
|
+
state.serviceEvidence = [
|
|
595
|
+
{ type: 'provisioning', status: 'passed', summary: 'Execution environment and mission context were provisioned.', artifactPath: primaryArtifact, at: now },
|
|
596
|
+
{ type: 'integration', status: 'passed', summary: 'Planner, executor, reviewer, and synthesizer pipeline completed.', artifactPath: primaryArtifact, at: now },
|
|
597
|
+
{ type: 'e2e', status: review.approved ? 'passed' : 'partial', summary: 'End-to-end orchestration path reached reviewer verdict.', artifactPath: primaryArtifact, at: now },
|
|
598
|
+
{ type: 'observability', status: 'passed', summary: 'Mission attempts, governance events, artifacts, and thought trace are persisted.', artifactPath: primaryArtifact, at: now },
|
|
599
|
+
{ type: 'operation', status: 'passed', summary: 'Operator can inspect the service/mission via OpenLife CLI artifacts.', artifactPath: primaryArtifact, command: `openlife task show ${taskId}`, at: now },
|
|
600
|
+
];
|
|
601
|
+
}
|
|
590
602
|
const serviceReport = this.serviceCompletionPolicy.evaluate(state);
|
|
591
603
|
if (!serviceReport.complete) {
|
|
592
604
|
state.status = 'failed';
|
|
@@ -14,6 +14,14 @@ class ParallelOrchestrationLoop {
|
|
|
14
14
|
this.arbitrationAgent = new ArbitrationAgent_1.ArbitrationAgent(brain);
|
|
15
15
|
this.scorecard = new ArbitrationScorecard_1.ArbitrationScorecard();
|
|
16
16
|
}
|
|
17
|
+
selectExecutor() {
|
|
18
|
+
const allowed = (process.env.OPENLIFE_ALLOWED_LLM_EXECUTORS || '').toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
|
|
19
|
+
if (allowed.includes('gemini'))
|
|
20
|
+
return 'gemini';
|
|
21
|
+
if ((process.env.GEMINI_API_KEY || '').trim())
|
|
22
|
+
return 'gemini';
|
|
23
|
+
return 'codex';
|
|
24
|
+
}
|
|
17
25
|
async runResearchBranches(goal, taskId, maxBranches = 3) {
|
|
18
26
|
const baseBranches = [
|
|
19
27
|
`[Ramo 1] Faça uma análise objetiva do objetivo:\n${goal}`,
|
|
@@ -22,14 +30,16 @@ class ParallelOrchestrationLoop {
|
|
|
22
30
|
`[Ramo 4] Faça uma leitura de consenso e decisão executiva do objetivo:\n${goal}`
|
|
23
31
|
];
|
|
24
32
|
const branches = baseBranches.slice(0, Math.max(1, maxBranches));
|
|
25
|
-
const
|
|
33
|
+
const executor = this.selectExecutor();
|
|
34
|
+
const results = await Promise.all(branches.map((prompt, index) => this.taskExecutor.execute(executor, prompt, `${taskId}_parallel_${index + 1}`)));
|
|
26
35
|
const outputs = results.map(r => r.stdout || r.stderr || '').filter(Boolean);
|
|
27
36
|
const scorecard = this.scorecard.score(outputs);
|
|
28
37
|
const arbitration = await this.arbitrationAgent.arbitrate(goal, outputs);
|
|
29
38
|
return {
|
|
30
39
|
branches: outputs,
|
|
31
40
|
arbitration,
|
|
32
|
-
scorecard
|
|
41
|
+
scorecard,
|
|
42
|
+
artifactPaths: results.map(r => r.artifactPath).filter(Boolean)
|
|
33
43
|
};
|
|
34
44
|
}
|
|
35
45
|
}
|
|
@@ -66,10 +66,13 @@ class RuntimePolicy {
|
|
|
66
66
|
const allowFiltered = allowed.size > 0 ? base.filter(e => allowed.has(e)) : base;
|
|
67
67
|
const preferred = allowFiltered.filter((executor) => {
|
|
68
68
|
const health = this.executorHealth.get(executor);
|
|
69
|
-
|
|
69
|
+
const geminiApiAvailable = executor === 'gemini' && !!(process.env.GEMINI_API_KEY || '').trim();
|
|
70
|
+
if (this.runtimeHealth.isCoolingDown(executor) && !geminiApiAvailable)
|
|
70
71
|
return false;
|
|
71
72
|
if (!health)
|
|
72
73
|
return true;
|
|
74
|
+
if (geminiApiAvailable)
|
|
75
|
+
return true;
|
|
73
76
|
return health.available !== false;
|
|
74
77
|
});
|
|
75
78
|
return {
|