@cccarv82/freya 3.5.1 → 3.5.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.
Files changed (2) hide show
  1. package/cli/web.js +150 -6
  2. package/package.json +1 -1
package/cli/web.js CHANGED
@@ -98,6 +98,113 @@ function newestFile(dir, prefix) {
98
98
  return files[0]?.p || null;
99
99
  }
100
100
 
101
+ // ---------------------------------------------------------------------------
102
+ // Daily-logs ↔ SQLite sync: keeps the daily_logs table in sync with .md files
103
+ // ---------------------------------------------------------------------------
104
+ function syncDailyLogs(workspaceDir) {
105
+ try {
106
+ const logsDir = path.join(workspaceDir, 'logs', 'daily');
107
+ if (!exists(logsDir)) return 0;
108
+ const files = fs.readdirSync(logsDir).filter(f => /^\d{4}-\d{2}-\d{2}\.md$/.test(f));
109
+ if (!files.length) return 0;
110
+
111
+ const upsert = dl.db.prepare(`
112
+ INSERT INTO daily_logs (date, raw_markdown) VALUES (?, ?)
113
+ ON CONFLICT(date) DO UPDATE SET raw_markdown = excluded.raw_markdown
114
+ `);
115
+
116
+ let synced = 0;
117
+ const tx = dl.db.transaction((fileList) => {
118
+ for (const file of fileList) {
119
+ const date = file.replace('.md', '');
120
+ const content = fs.readFileSync(path.join(logsDir, file), 'utf8');
121
+ upsert.run(date, content);
122
+ synced++;
123
+ }
124
+ });
125
+ tx(files);
126
+ return synced;
127
+ } catch (e) {
128
+ console.error('[sync] Daily-logs sync failed:', e.message);
129
+ return 0;
130
+ }
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Build real data context for Orchestrator (chat) — feeds SQLite + daily logs
135
+ // as plain-text so the LLM has actual data to synthesize answers from
136
+ // ---------------------------------------------------------------------------
137
+ function buildDataContext(workspaceDir, maxDays) {
138
+ maxDays = maxDays || 7;
139
+ const parts = [];
140
+
141
+ // 1. Recent daily logs (from filesystem — most up-to-date source)
142
+ try {
143
+ const logsDir = path.join(workspaceDir, 'logs', 'daily');
144
+ if (exists(logsDir)) {
145
+ const files = fs.readdirSync(logsDir)
146
+ .filter(f => /^\d{4}-\d{2}-\d{2}\.md$/.test(f))
147
+ .sort()
148
+ .slice(-maxDays);
149
+ if (files.length) {
150
+ parts.push('\n\n[DAILY LOGS — ÚLTIMOS ' + files.length + ' DIAS]');
151
+ for (const file of files) {
152
+ const date = file.replace('.md', '');
153
+ const content = fs.readFileSync(path.join(logsDir, file), 'utf8');
154
+ // Truncate very large logs to avoid token overflow
155
+ const trimmed = content.length > 8000 ? content.slice(0, 8000) + '\n...(truncado)' : content;
156
+ parts.push(`\n--- LOG ${date} ---\n${trimmed}`);
157
+ }
158
+ }
159
+ }
160
+ } catch (e) {
161
+ console.error('[context] Failed to read daily logs:', e.message);
162
+ }
163
+
164
+ // 2. Pending tasks from SQLite
165
+ try {
166
+ const tasks = dl.db.prepare("SELECT id, description, category, status, project_slug, created_at, due_date FROM tasks WHERE status = 'PENDING' ORDER BY created_at DESC LIMIT 50").all();
167
+ if (tasks.length) {
168
+ parts.push('\n\n[TASKS PENDENTES — SQLite (' + tasks.length + ' tasks)]');
169
+ for (const t of tasks) {
170
+ parts.push(`• [${t.category}] ${t.description} (projeto: ${t.project_slug || 'N/A'}, criado: ${t.created_at || '?'}${t.due_date ? ', prazo: ' + t.due_date : ''})`);
171
+ }
172
+ } else {
173
+ parts.push('\n\n[TASKS PENDENTES — SQLite: nenhuma task registrada]');
174
+ }
175
+ } catch (e) {
176
+ parts.push('\n\n[TASKS: erro ao consultar SQLite — ' + e.message + ']');
177
+ }
178
+
179
+ // 3. Open blockers from SQLite
180
+ try {
181
+ const blockers = dl.db.prepare("SELECT id, title, severity, status, project_slug, owner, next_action, created_at FROM blockers WHERE status IN ('OPEN','MITIGATING') ORDER BY created_at DESC LIMIT 30").all();
182
+ if (blockers.length) {
183
+ parts.push('\n\n[BLOCKERS ABERTOS — SQLite (' + blockers.length + ' blockers)]');
184
+ for (const b of blockers) {
185
+ parts.push(`• [${b.severity}] ${b.title} (projeto: ${b.project_slug || 'N/A'}, status: ${b.status}, owner: ${b.owner || '?'})`);
186
+ }
187
+ } else {
188
+ parts.push('\n\n[BLOCKERS ABERTOS — SQLite: nenhum blocker registrado]');
189
+ }
190
+ } catch (e) {
191
+ parts.push('\n\n[BLOCKERS: erro ao consultar SQLite — ' + e.message + ']');
192
+ }
193
+
194
+ // 4. Active projects
195
+ try {
196
+ const projects = dl.db.prepare("SELECT slug, client, name FROM projects WHERE is_active = 1 ORDER BY slug").all();
197
+ if (projects.length) {
198
+ parts.push('\n\n[PROJETOS ATIVOS — SQLite (' + projects.length + ')]');
199
+ for (const p of projects) {
200
+ parts.push(`• ${p.slug} — ${p.name || p.client || 'sem nome'}`);
201
+ }
202
+ }
203
+ } catch { /* ignore */ }
204
+
205
+ return parts.join('\n');
206
+ }
207
+
101
208
  function settingsPath(workspaceDir) {
102
209
  return path.join(workspaceDir, 'data', 'settings', 'settings.json');
103
210
  }
@@ -2755,6 +2862,14 @@ async function cmdWeb({ port, dir, open, dev }) {
2755
2862
  await autoUpdate(wsDir);
2756
2863
  } catch { /* non-fatal */ }
2757
2864
 
2865
+ // Sync daily log .md files → SQLite daily_logs table on startup
2866
+ try {
2867
+ const synced = syncDailyLogs(wsDir);
2868
+ if (synced > 0) console.log(`[FREYA] Synced ${synced} daily logs to SQLite`);
2869
+ } catch (e) {
2870
+ console.error('[FREYA] Warning: daily-logs sync failed:', e.message || String(e));
2871
+ }
2872
+
2758
2873
  const host = '127.0.0.1';
2759
2874
 
2760
2875
  const server = http.createServer(async (req, res) => {
@@ -3736,6 +3851,14 @@ async function cmdWeb({ port, dir, open, dev }) {
3736
3851
  }
3737
3852
  }
3738
3853
 
3854
+ // Sync this daily log file to SQLite so chat/RAG can find it
3855
+ try {
3856
+ const upsert = dl.db.prepare(`INSERT INTO daily_logs (date, raw_markdown) VALUES (?, ?) ON CONFLICT(date) DO UPDATE SET raw_markdown = excluded.raw_markdown`);
3857
+ upsert.run(d, fs.readFileSync(file, 'utf8'));
3858
+ } catch (syncErr) {
3859
+ console.error('[inbox] Failed to sync daily log to SQLite:', syncErr.message);
3860
+ }
3861
+
3739
3862
  return safeJson(res, 200, { ok: true, file: path.relative(workspaceDir, file).replace(/\\/g, '/'), appended: true });
3740
3863
  }
3741
3864
 
@@ -4277,6 +4400,12 @@ async function cmdWeb({ port, dir, open, dev }) {
4277
4400
  return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
4278
4401
  }).join('');
4279
4402
 
4403
+ // Ensure daily logs are synced to SQLite before querying
4404
+ try { syncDailyLogs(workspaceDir); } catch { /* non-fatal */ }
4405
+
4406
+ // Build real data context from SQLite + daily log files
4407
+ const dataContext = buildDataContext(workspaceDir, 7);
4408
+
4280
4409
  // V2 RAG Context (graceful fallback if embedder/sharp not available)
4281
4410
  const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
4282
4411
  let ragContext = '';
@@ -4301,8 +4430,23 @@ async function cmdWeb({ port, dir, open, dev }) {
4301
4430
  }
4302
4431
  }
4303
4432
 
4304
- // System instructions (small, always fits in -p)
4305
- const oracleSysInstr = `Você é o Orchestrator do sistema F.R.E.Y.A.\n\nVocê NÃO é o Oracle. Você é o agente principal que COORDENA todos os sub-agentes.\nSiga o fluxo definido no master.mdc: analise o intent do usuário, execute o plano internamente (Oracle para buscar dados, SM Agent para sintetizar), e SEMPRE retorne uma resposta FINAL em linguagem natural, estruturada e consultiva para o usuário.\n\nNUNCA exponha JSONs brutos, hierarquia interna de agentes, ou peça ao usuário para "chamar outro agente".\nVocê DEVE sintetizar os dados recuperados em uma resposta clara, organizada e útil.\n\nIdioma: Português do Brasil.\n${ragContext}${imageContext}`;
4433
+ // System instructions includes REAL data context so the Orchestrator
4434
+ // can synthesize answers without needing to "call" sub-agents at runtime
4435
+ const oracleSysInstr = `Você é FREYA — Assistente Responsiva com Otimização Aprimorada.
4436
+
4437
+ PAPEL: Você é o agente principal do sistema. Responda SEMPRE em linguagem natural, estruturada e consultiva.
4438
+
4439
+ REGRAS ABSOLUTAS:
4440
+ - NUNCA exponha JSONs brutos, nomes de agentes internos (Oracle, SM Agent, Ingestor), ou hierarquia de roteamento.
4441
+ - NUNCA peça ao usuário para "chamar outro agente" ou "invocar o Orchestrator".
4442
+ - NUNCA diga "como agente X, não posso...". Você é FREYA, um sistema único e coeso.
4443
+ - SEMPRE sintetize os dados abaixo em respostas úteis, organizadas e em português brasileiro.
4444
+ - Use a estrutura: Contexto → Análise → Recomendações → Próximos passos.
4445
+ - Termine com: — FREYA\\nAssistente Responsiva com Otimização Aprimorada
4446
+
4447
+ DADOS REAIS DO WORKSPACE (use estes dados para responder):
4448
+ ${dataContext}
4449
+ ${ragContext}${imageContext}`;
4306
4450
 
4307
4451
  const cmd = process.env.COPILOT_CMD || 'copilot';
4308
4452
 
@@ -4318,15 +4462,15 @@ async function cmdWeb({ port, dir, open, dev }) {
4318
4462
  }
4319
4463
  }
4320
4464
 
4321
- // ENAMETOOLONG fix: when prompt is large, write user query to temp file
4465
+ // ENAMETOOLONG fix: when prompt is large, write full prompt to temp file
4322
4466
  const fullOraclePrompt = `${oracleSysInstr}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
4323
4467
  const SAFE_ARG_LEN = 24000;
4324
4468
  let oracleTmpFile = null;
4325
4469
  let r;
4326
4470
  if (fullOraclePrompt.length > SAFE_ARG_LEN) {
4327
- oracleTmpFile = path.join(os.tmpdir(), `freya-oracle-input-${Date.now()}.txt`);
4328
- fs.writeFileSync(oracleTmpFile, query, 'utf8');
4329
- const filePrompt = `${oracleSysInstr}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\nA consulta do usuário é grande e foi salva no arquivo abaixo. LEIA o conteúdo completo do arquivo e responda com base nele.\nARQUIVO: ${oracleTmpFile}\n\nIMPORTANTE: NÃO descreva o arquivo. LEIA e RESPONDA à consulta.\n`;
4471
+ oracleTmpFile = path.join(os.tmpdir(), `freya-orchestrator-${Date.now()}.txt`);
4472
+ fs.writeFileSync(oracleTmpFile, fullOraclePrompt, 'utf8');
4473
+ const filePrompt = `Leia o arquivo abaixo que contém suas instruções completas, regras, dados do workspace e a consulta do usuário. Siga TODAS as instruções contidas nele.\nARQUIVO: ${oracleTmpFile}\n\nIMPORTANTE: Leia o arquivo INTEIRO e responda à consulta do usuário que está no final do arquivo.`;
4330
4474
  copilotArgs.push('--add-dir', os.tmpdir());
4331
4475
  copilotArgs.push('--allow-all-tools', '-p', filePrompt);
4332
4476
  r = await run(cmd, copilotArgs, workspaceDir, oracleEnv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "3.5.1",
3
+ "version": "3.5.2",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",