@cccarv82/freya 3.7.3 → 3.7.4
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/cli/web.js +90 -25
- package/package.json +1 -1
package/cli/web.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
-
const { spawn } = require('child_process');
|
|
8
|
+
const { spawn, execSync } = require('child_process');
|
|
9
9
|
const { searchWorkspace } = require('../scripts/lib/search-utils');
|
|
10
10
|
const { searchIndex } = require('../scripts/lib/index-utils');
|
|
11
11
|
const { initWorkspace } = require('./init');
|
|
@@ -278,7 +278,7 @@ async function backgroundIngestFromChat(workspaceDir, userQuery) {
|
|
|
278
278
|
if (!INGEST_SIGNALS.test(userQuery)) return;
|
|
279
279
|
|
|
280
280
|
try {
|
|
281
|
-
const
|
|
281
|
+
const copilotResolved = getCopilotCmd();
|
|
282
282
|
|
|
283
283
|
// Build a minimal planner prompt
|
|
284
284
|
const schema = {
|
|
@@ -290,9 +290,9 @@ async function backgroundIngestFromChat(workspaceDir, userQuery) {
|
|
|
290
290
|
const prompt = `Você é o planner do sistema F.R.E.Y.A.\n\nAnalise o texto abaixo e extraia APENAS tarefas e blockers explícitos.\nSe NÃO houver tarefas ou blockers claros, retorne: {"actions":[]}\nRetorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\nNÃO use code fences. NÃO inclua texto extra.\n\nTEXTO:\n${userQuery}\n`;
|
|
291
291
|
|
|
292
292
|
const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
293
|
-
const
|
|
293
|
+
const { cmd: spawnCmd, args: baseArgsPrefix } = copilotSpawnArgs(copilotResolved, ['-s', '--no-color', '--stream', 'off', '-p', prompt]);
|
|
294
294
|
|
|
295
|
-
const r = await run(
|
|
295
|
+
const r = await run(spawnCmd, baseArgsPrefix, workspaceDir, agentEnv);
|
|
296
296
|
const out = (r.stdout + r.stderr).trim();
|
|
297
297
|
if (r.code !== 0 || !out) return;
|
|
298
298
|
|
|
@@ -902,6 +902,67 @@ function readBody(req, maxBytes = 4 * 1024 * 1024) {
|
|
|
902
902
|
});
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
+
/**
|
|
906
|
+
* Resolve the full path to the copilot CLI.
|
|
907
|
+
* On Windows with fnm/nvm, npm global .cmd shims aren't in cmd.exe's PATH,
|
|
908
|
+
* so we use PowerShell to discover the real path. Result is cached.
|
|
909
|
+
*/
|
|
910
|
+
let _copilotPathCache = null;
|
|
911
|
+
function getCopilotCmd() {
|
|
912
|
+
if (_copilotPathCache !== null) return _copilotPathCache;
|
|
913
|
+
|
|
914
|
+
// User override
|
|
915
|
+
if (process.env.COPILOT_CMD) {
|
|
916
|
+
_copilotPathCache = process.env.COPILOT_CMD;
|
|
917
|
+
return _copilotPathCache;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (process.platform === 'win32') {
|
|
921
|
+
// Try 'where' first (system PATH)
|
|
922
|
+
try {
|
|
923
|
+
const p = execSync('where copilot 2>nul', { encoding: 'utf8', timeout: 5000 }).trim().split(/\r?\n/)[0];
|
|
924
|
+
if (p && fs.existsSync(p)) { _copilotPathCache = p; return p; }
|
|
925
|
+
} catch { }
|
|
926
|
+
// Use PowerShell to resolve (works with fnm, nvm-windows, etc.)
|
|
927
|
+
try {
|
|
928
|
+
const p = execSync(
|
|
929
|
+
'powershell.exe -NoProfile -Command "(Get-Command copilot -ErrorAction SilentlyContinue).Source"',
|
|
930
|
+
{ encoding: 'utf8', timeout: 10000 }
|
|
931
|
+
).trim();
|
|
932
|
+
if (p && fs.existsSync(p)) { _copilotPathCache = p; return p; }
|
|
933
|
+
} catch { }
|
|
934
|
+
// Try gh
|
|
935
|
+
try {
|
|
936
|
+
const p = execSync(
|
|
937
|
+
'powershell.exe -NoProfile -Command "(Get-Command gh -ErrorAction SilentlyContinue).Source"',
|
|
938
|
+
{ encoding: 'utf8', timeout: 10000 }
|
|
939
|
+
).trim();
|
|
940
|
+
if (p && fs.existsSync(p)) { _copilotPathCache = `gh-copilot:${p}`; return _copilotPathCache; }
|
|
941
|
+
} catch { }
|
|
942
|
+
} else {
|
|
943
|
+
try {
|
|
944
|
+
const p = execSync('which copilot 2>/dev/null', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
945
|
+
if (p) { _copilotPathCache = p; return p; }
|
|
946
|
+
} catch { }
|
|
947
|
+
try {
|
|
948
|
+
const p = execSync('which gh 2>/dev/null', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
949
|
+
if (p) { _copilotPathCache = `gh-copilot:${p}`; return _copilotPathCache; }
|
|
950
|
+
} catch { }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
_copilotPathCache = 'copilot'; // fallback
|
|
954
|
+
return _copilotPathCache;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** Helper: build cmd + args for copilot or gh copilot */
|
|
958
|
+
function copilotSpawnArgs(copilotCmd, extraArgs) {
|
|
959
|
+
if (copilotCmd.startsWith('gh-copilot:')) {
|
|
960
|
+
const ghPath = copilotCmd.slice('gh-copilot:'.length);
|
|
961
|
+
return { cmd: ghPath, args: ['copilot', ...extraArgs] };
|
|
962
|
+
}
|
|
963
|
+
return { cmd: copilotCmd, args: extraArgs };
|
|
964
|
+
}
|
|
965
|
+
|
|
905
966
|
function run(cmd, args, cwd, extraEnv, stdinData) {
|
|
906
967
|
return new Promise((resolve) => {
|
|
907
968
|
let child;
|
|
@@ -1008,7 +1069,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1008
1069
|
if (!q) return { ok: false, error: 'Missing query' };
|
|
1009
1070
|
|
|
1010
1071
|
const limit = Math.max(1, Math.min(20, Number(opts.limit || 8)));
|
|
1011
|
-
const
|
|
1072
|
+
const copilotResolved = getCopilotCmd();
|
|
1012
1073
|
|
|
1013
1074
|
const prompt = [
|
|
1014
1075
|
'Você é um buscador local de arquivos.',
|
|
@@ -1027,7 +1088,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1027
1088
|
'A lista deve estar ordenada por relevância.'
|
|
1028
1089
|
].join('\n');
|
|
1029
1090
|
|
|
1030
|
-
const args = [
|
|
1091
|
+
const { cmd: spawnCmd, args: spawnArgs } = copilotSpawnArgs(copilotResolved, [
|
|
1031
1092
|
'-s',
|
|
1032
1093
|
'--no-color',
|
|
1033
1094
|
'--stream',
|
|
@@ -1037,9 +1098,9 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1037
1098
|
'--allow-all-tools',
|
|
1038
1099
|
'--add-dir',
|
|
1039
1100
|
workspaceDir
|
|
1040
|
-
];
|
|
1101
|
+
]);
|
|
1041
1102
|
|
|
1042
|
-
const r = await run(
|
|
1103
|
+
const r = await run(spawnCmd, spawnArgs, workspaceDir);
|
|
1043
1104
|
const out = (r.stdout + r.stderr).trim();
|
|
1044
1105
|
if (r.code !== 0) return { ok: false, error: out || 'Copilot returned non-zero exit code.' };
|
|
1045
1106
|
|
|
@@ -4081,8 +4142,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4081
4142
|
// Build the system instructions (small, always fits in -p)
|
|
4082
4143
|
const sysInstructions = `Você é o planner do sistema F.R.E.Y.A.\n\nContexto: vamos receber um input bruto do usuário e propor ações estruturadas.\nRegras: siga os arquivos de regras abaixo.\nSaída: retorne APENAS JSON válido no formato: ${JSON.stringify(schema)}\n\nRestrições:\n- NÃO use code fences (\`\`\`)\n- NÃO inclua texto extra antes/depois do JSON\n- NÃO use quebras de linha dentro de strings (transforme em uma frase única)${planImageContext}`;
|
|
4083
4144
|
|
|
4084
|
-
|
|
4085
|
-
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
4145
|
+
const copilotResolved = getCopilotCmd();
|
|
4086
4146
|
|
|
4087
4147
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4088
4148
|
const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
@@ -4097,8 +4157,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4097
4157
|
|
|
4098
4158
|
try {
|
|
4099
4159
|
let r;
|
|
4100
|
-
const
|
|
4101
|
-
if (planImageDir)
|
|
4160
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4161
|
+
if (planImageDir) copilotExtra.push('--add-dir', planImageDir);
|
|
4102
4162
|
|
|
4103
4163
|
if (needsFile) {
|
|
4104
4164
|
// Write ONLY the user text to a temp file; keep instructions in -p
|
|
@@ -4112,15 +4172,18 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4112
4172
|
const rulesTmpFile = path.join(os.tmpdir(), `freya-rules-${Date.now()}.txt`);
|
|
4113
4173
|
fs.writeFileSync(rulesTmpFile, rulesText, 'utf8');
|
|
4114
4174
|
const minPrompt = `${sysInstructions}\n\nREGRAS: Leia as regras do arquivo: ${rulesTmpFile}\n\nINPUT DO USUÁRIO:\nO texto do usuário é MUITO GRANDE e foi salvo no arquivo abaixo. Você DEVE ler o conteúdo completo deste arquivo e processar TODO o conteúdo como input do usuário.\nARQUIVO: ${tmpFile}\n\nIMPORTANTE: NÃO descreva os arquivos. LEIA os conteúdos e processe-os gerando as ações JSON conforme as regras.\n`;
|
|
4115
|
-
|
|
4116
|
-
|
|
4175
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4176
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', minPrompt, '--allow-all-tools']);
|
|
4177
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4117
4178
|
try { fs.unlinkSync(rulesTmpFile); } catch (_) { /* ignore */ }
|
|
4118
4179
|
} else {
|
|
4119
|
-
|
|
4120
|
-
|
|
4180
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4181
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', filePrompt, '--allow-all-tools']);
|
|
4182
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4121
4183
|
}
|
|
4122
4184
|
} else {
|
|
4123
|
-
|
|
4185
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', fullPrompt, '--allow-all-tools']);
|
|
4186
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4124
4187
|
}
|
|
4125
4188
|
const out = (r.stdout + r.stderr).trim();
|
|
4126
4189
|
if (r.code !== 0) {
|
|
@@ -4614,17 +4677,17 @@ DADOS REAIS DO WORKSPACE (use estes dados para responder):
|
|
|
4614
4677
|
${dataContext}
|
|
4615
4678
|
${imageContext}`;
|
|
4616
4679
|
|
|
4617
|
-
const
|
|
4680
|
+
const copilotResolved = getCopilotCmd();
|
|
4618
4681
|
|
|
4619
4682
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4620
4683
|
const oracleEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
4621
4684
|
try {
|
|
4622
|
-
const
|
|
4685
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4623
4686
|
// Allow Copilot to access the image file via its built-in tools
|
|
4624
4687
|
if (imagePath) {
|
|
4625
4688
|
const absImg = path.isAbsolute(imagePath) ? imagePath : path.join(workspaceDir, imagePath);
|
|
4626
4689
|
if (exists(absImg)) {
|
|
4627
|
-
|
|
4690
|
+
copilotExtra.push('--add-dir', path.dirname(absImg));
|
|
4628
4691
|
}
|
|
4629
4692
|
}
|
|
4630
4693
|
|
|
@@ -4637,12 +4700,14 @@ ${imageContext}`;
|
|
|
4637
4700
|
oracleTmpFile = path.join(os.tmpdir(), `freya-orchestrator-${Date.now()}.txt`);
|
|
4638
4701
|
fs.writeFileSync(oracleTmpFile, fullOraclePrompt, 'utf8');
|
|
4639
4702
|
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.`;
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4703
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4704
|
+
copilotExtra.push('--allow-all-tools', '-p', filePrompt);
|
|
4705
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, copilotExtra);
|
|
4706
|
+
r = await run(sc, sa, workspaceDir, oracleEnv);
|
|
4643
4707
|
} else {
|
|
4644
|
-
|
|
4645
|
-
|
|
4708
|
+
copilotExtra.push('--allow-all-tools', '-p', fullOraclePrompt);
|
|
4709
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, copilotExtra);
|
|
4710
|
+
r = await run(sc, sa, workspaceDir, oracleEnv);
|
|
4646
4711
|
}
|
|
4647
4712
|
if (oracleTmpFile) { try { fs.unlinkSync(oracleTmpFile); } catch (_) { /* ignore */ } }
|
|
4648
4713
|
const out = (r.stdout + r.stderr).trim();
|
package/package.json
CHANGED