@cccarv82/freya 3.7.2 → 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 +92 -33
- 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;
|
|
@@ -909,14 +970,8 @@ function run(cmd, args, cwd, extraEnv, stdinData) {
|
|
|
909
970
|
const env = extraEnv ? { ...process.env, ...extraEnv } : process.env;
|
|
910
971
|
|
|
911
972
|
try {
|
|
912
|
-
//
|
|
913
|
-
|
|
914
|
-
if (process.platform === 'win32') {
|
|
915
|
-
const comspec = process.env.ComSpec || 'cmd.exe';
|
|
916
|
-
child = spawn(comspec, ['/d', '/s', '/c', cmd, ...args], { cwd, shell: false, env });
|
|
917
|
-
} else {
|
|
918
|
-
child = spawn(cmd, args, { cwd, shell: false, env });
|
|
919
|
-
}
|
|
973
|
+
// Use shell: true on all platforms — ensures .cmd/.bat shims (npm globals) are found on Windows
|
|
974
|
+
child = spawn(cmd, args, { cwd, shell: true, env, windowsHide: true });
|
|
920
975
|
} catch (e) {
|
|
921
976
|
return resolve({ code: 1, stdout: '', stderr: e.message || String(e) });
|
|
922
977
|
}
|
|
@@ -1014,7 +1069,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1014
1069
|
if (!q) return { ok: false, error: 'Missing query' };
|
|
1015
1070
|
|
|
1016
1071
|
const limit = Math.max(1, Math.min(20, Number(opts.limit || 8)));
|
|
1017
|
-
const
|
|
1072
|
+
const copilotResolved = getCopilotCmd();
|
|
1018
1073
|
|
|
1019
1074
|
const prompt = [
|
|
1020
1075
|
'Você é um buscador local de arquivos.',
|
|
@@ -1033,7 +1088,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1033
1088
|
'A lista deve estar ordenada por relevância.'
|
|
1034
1089
|
].join('\n');
|
|
1035
1090
|
|
|
1036
|
-
const args = [
|
|
1091
|
+
const { cmd: spawnCmd, args: spawnArgs } = copilotSpawnArgs(copilotResolved, [
|
|
1037
1092
|
'-s',
|
|
1038
1093
|
'--no-color',
|
|
1039
1094
|
'--stream',
|
|
@@ -1043,9 +1098,9 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1043
1098
|
'--allow-all-tools',
|
|
1044
1099
|
'--add-dir',
|
|
1045
1100
|
workspaceDir
|
|
1046
|
-
];
|
|
1101
|
+
]);
|
|
1047
1102
|
|
|
1048
|
-
const r = await run(
|
|
1103
|
+
const r = await run(spawnCmd, spawnArgs, workspaceDir);
|
|
1049
1104
|
const out = (r.stdout + r.stderr).trim();
|
|
1050
1105
|
if (r.code !== 0) return { ok: false, error: out || 'Copilot returned non-zero exit code.' };
|
|
1051
1106
|
|
|
@@ -4087,8 +4142,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4087
4142
|
// Build the system instructions (small, always fits in -p)
|
|
4088
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}`;
|
|
4089
4144
|
|
|
4090
|
-
|
|
4091
|
-
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
4145
|
+
const copilotResolved = getCopilotCmd();
|
|
4092
4146
|
|
|
4093
4147
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4094
4148
|
const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
@@ -4103,8 +4157,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4103
4157
|
|
|
4104
4158
|
try {
|
|
4105
4159
|
let r;
|
|
4106
|
-
const
|
|
4107
|
-
if (planImageDir)
|
|
4160
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4161
|
+
if (planImageDir) copilotExtra.push('--add-dir', planImageDir);
|
|
4108
4162
|
|
|
4109
4163
|
if (needsFile) {
|
|
4110
4164
|
// Write ONLY the user text to a temp file; keep instructions in -p
|
|
@@ -4118,15 +4172,18 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4118
4172
|
const rulesTmpFile = path.join(os.tmpdir(), `freya-rules-${Date.now()}.txt`);
|
|
4119
4173
|
fs.writeFileSync(rulesTmpFile, rulesText, 'utf8');
|
|
4120
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`;
|
|
4121
|
-
|
|
4122
|
-
|
|
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);
|
|
4123
4178
|
try { fs.unlinkSync(rulesTmpFile); } catch (_) { /* ignore */ }
|
|
4124
4179
|
} else {
|
|
4125
|
-
|
|
4126
|
-
|
|
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);
|
|
4127
4183
|
}
|
|
4128
4184
|
} else {
|
|
4129
|
-
|
|
4185
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', fullPrompt, '--allow-all-tools']);
|
|
4186
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4130
4187
|
}
|
|
4131
4188
|
const out = (r.stdout + r.stderr).trim();
|
|
4132
4189
|
if (r.code !== 0) {
|
|
@@ -4620,17 +4677,17 @@ DADOS REAIS DO WORKSPACE (use estes dados para responder):
|
|
|
4620
4677
|
${dataContext}
|
|
4621
4678
|
${imageContext}`;
|
|
4622
4679
|
|
|
4623
|
-
const
|
|
4680
|
+
const copilotResolved = getCopilotCmd();
|
|
4624
4681
|
|
|
4625
4682
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4626
4683
|
const oracleEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
4627
4684
|
try {
|
|
4628
|
-
const
|
|
4685
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4629
4686
|
// Allow Copilot to access the image file via its built-in tools
|
|
4630
4687
|
if (imagePath) {
|
|
4631
4688
|
const absImg = path.isAbsolute(imagePath) ? imagePath : path.join(workspaceDir, imagePath);
|
|
4632
4689
|
if (exists(absImg)) {
|
|
4633
|
-
|
|
4690
|
+
copilotExtra.push('--add-dir', path.dirname(absImg));
|
|
4634
4691
|
}
|
|
4635
4692
|
}
|
|
4636
4693
|
|
|
@@ -4643,12 +4700,14 @@ ${imageContext}`;
|
|
|
4643
4700
|
oracleTmpFile = path.join(os.tmpdir(), `freya-orchestrator-${Date.now()}.txt`);
|
|
4644
4701
|
fs.writeFileSync(oracleTmpFile, fullOraclePrompt, 'utf8');
|
|
4645
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.`;
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
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);
|
|
4649
4707
|
} else {
|
|
4650
|
-
|
|
4651
|
-
|
|
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);
|
|
4652
4711
|
}
|
|
4653
4712
|
if (oracleTmpFile) { try { fs.unlinkSync(oracleTmpFile); } catch (_) { /* ignore */ } }
|
|
4654
4713
|
const out = (r.stdout + r.stderr).trim();
|
package/package.json
CHANGED