@cccarv82/freya 3.5.0 → 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.
@@ -149,6 +149,18 @@ Combine all results into a unified, natural response:
149
149
  - Always cite sources at the end
150
150
  - Always provide next steps
151
151
 
152
+ <critical-rule>
153
+ **NUNCA EXPONHA A ARQUITETURA INTERNA AO USUÁRIO:**
154
+ - NUNCA mencione nomes de agentes (Oracle, SM Agent, Ingestor, Analytics Agent, Coach)
155
+ - NUNCA explique o fluxo de roteamento ou hierarquia de agentes
156
+ - NUNCA peça ao usuário para "chamar outro agente" ou "invocar o Orchestrator"
157
+ - NUNCA mostre JSONs brutos como resposta final
158
+ - NUNCA diga "como Oracle, não posso..." ou "minha função é apenas..."
159
+ - Você é FREYA para o usuário. Um sistema único e coeso. A orquestração interna é invisível.
160
+ - Se um sub-agente retorna dados brutos, VOCÊ sintetiza em linguagem natural antes de responder.
161
+ - Se um sub-agente se recusa a sintetizar, VOCÊ faz a síntese — é sua responsabilidade final.
162
+ </critical-rule>
163
+
152
164
  ## Phase 5: Session Memory
153
165
 
154
166
  Maintain awareness of what was discussed in this session:
@@ -120,3 +120,4 @@ When returning data, format consistently:
120
120
  - **Always include sources**: Every piece of data must be traceable to a file or table.
121
121
  - **Dates in dd/mm/aaaa**: When presenting dates to humans, use Brazilian format.
122
122
  - **No synthesis**: You retrieve and format. You do NOT analyze or recommend. That's the Super Agent's job.
123
+ - **NEVER talk to the user directly**: You are a utility agent. When running inside the Orchestrator context, your data is consumed internally. The Orchestrator handles all user-facing communication. NEVER explain your role, hierarchy, or suggest the user "call another agent".
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
 
@@ -4265,7 +4388,11 @@ async function cmdWeb({ port, dir, open, dev }) {
4265
4388
  const files = [
4266
4389
  path.join(rulesBase, 'freya.mdc'),
4267
4390
  path.join(rulesBase, 'agents', 'master.mdc'),
4268
- path.join(rulesBase, 'agents', 'oracle.mdc')
4391
+ path.join(rulesBase, 'agents', 'sm-agent.mdc'),
4392
+ path.join(rulesBase, 'agents', 'analytics-agent.mdc'),
4393
+ path.join(rulesBase, 'agents', 'oracle.mdc'),
4394
+ path.join(rulesBase, 'agents', 'coach.mdc'),
4395
+ path.join(rulesBase, 'agents', 'ingestor.mdc')
4269
4396
  ].filter(exists);
4270
4397
 
4271
4398
  const rulesText = files.map((p) => {
@@ -4273,6 +4400,12 @@ async function cmdWeb({ port, dir, open, dev }) {
4273
4400
  return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
4274
4401
  }).join('');
4275
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
+
4276
4409
  // V2 RAG Context (graceful fallback if embedder/sharp not available)
4277
4410
  const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
4278
4411
  let ragContext = '';
@@ -4297,8 +4430,23 @@ async function cmdWeb({ port, dir, open, dev }) {
4297
4430
  }
4298
4431
  }
4299
4432
 
4300
- // System instructions (small, always fits in -p)
4301
- const oracleSysInstr = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\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}`;
4302
4450
 
4303
4451
  const cmd = process.env.COPILOT_CMD || 'copilot';
4304
4452
 
@@ -4314,15 +4462,15 @@ async function cmdWeb({ port, dir, open, dev }) {
4314
4462
  }
4315
4463
  }
4316
4464
 
4317
- // 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
4318
4466
  const fullOraclePrompt = `${oracleSysInstr}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
4319
4467
  const SAFE_ARG_LEN = 24000;
4320
4468
  let oracleTmpFile = null;
4321
4469
  let r;
4322
4470
  if (fullOraclePrompt.length > SAFE_ARG_LEN) {
4323
- oracleTmpFile = path.join(os.tmpdir(), `freya-oracle-input-${Date.now()}.txt`);
4324
- fs.writeFileSync(oracleTmpFile, query, 'utf8');
4325
- 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.`;
4326
4474
  copilotArgs.push('--add-dir', os.tmpdir());
4327
4475
  copilotArgs.push('--allow-all-tools', '-p', filePrompt);
4328
4476
  r = await run(cmd, copilotArgs, workspaceDir, oracleEnv);
@@ -4333,7 +4481,7 @@ async function cmdWeb({ port, dir, open, dev }) {
4333
4481
  if (oracleTmpFile) { try { fs.unlinkSync(oracleTmpFile); } catch (_) { /* ignore */ } }
4334
4482
  const out = (r.stdout + r.stderr).trim();
4335
4483
  if (r.code !== 0) {
4336
- return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
4484
+ return safeJson(res, 200, { ok: false, answer: 'Falha no processamento do agente FREYA:\n' + (out || 'Exit code != 0'), sessionId });
4337
4485
  }
4338
4486
  return safeJson(res, 200, { ok: true, answer: out, sessionId });
4339
4487
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "3.5.0",
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",