@cccarv82/freya 3.7.3 → 3.7.5
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 +105 -27
- 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,8 +970,21 @@ function run(cmd, args, cwd, extraEnv, stdinData) {
|
|
|
909
970
|
const env = extraEnv ? { ...process.env, ...extraEnv } : process.env;
|
|
910
971
|
|
|
911
972
|
try {
|
|
912
|
-
|
|
913
|
-
|
|
973
|
+
if (process.platform === 'win32') {
|
|
974
|
+
// Use PowerShell — fnm/nvm set PATH only in PowerShell profile,
|
|
975
|
+
// so cmd.exe (shell:true) can't find npm global .cmd shims like copilot.
|
|
976
|
+
// Do NOT use -NoProfile: the profile is what loads fnm/nvm PATH entries.
|
|
977
|
+
const escapedArgs = args.map(a => {
|
|
978
|
+
const escaped = String(a).replace(/'/g, "''");
|
|
979
|
+
return `'${escaped}'`;
|
|
980
|
+
});
|
|
981
|
+
const psCommand = `& '${cmd}' ${escapedArgs.join(' ')}`;
|
|
982
|
+
child = spawn('powershell.exe', [
|
|
983
|
+
'-NoLogo', '-Command', psCommand
|
|
984
|
+
], { cwd, env, windowsHide: true });
|
|
985
|
+
} else {
|
|
986
|
+
child = spawn(cmd, args, { cwd, shell: true, env });
|
|
987
|
+
}
|
|
914
988
|
} catch (e) {
|
|
915
989
|
return resolve({ code: 1, stdout: '', stderr: e.message || String(e) });
|
|
916
990
|
}
|
|
@@ -1008,7 +1082,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1008
1082
|
if (!q) return { ok: false, error: 'Missing query' };
|
|
1009
1083
|
|
|
1010
1084
|
const limit = Math.max(1, Math.min(20, Number(opts.limit || 8)));
|
|
1011
|
-
const
|
|
1085
|
+
const copilotResolved = getCopilotCmd();
|
|
1012
1086
|
|
|
1013
1087
|
const prompt = [
|
|
1014
1088
|
'Você é um buscador local de arquivos.',
|
|
@@ -1027,7 +1101,7 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1027
1101
|
'A lista deve estar ordenada por relevância.'
|
|
1028
1102
|
].join('\n');
|
|
1029
1103
|
|
|
1030
|
-
const args = [
|
|
1104
|
+
const { cmd: spawnCmd, args: spawnArgs } = copilotSpawnArgs(copilotResolved, [
|
|
1031
1105
|
'-s',
|
|
1032
1106
|
'--no-color',
|
|
1033
1107
|
'--stream',
|
|
@@ -1037,9 +1111,9 @@ async function copilotSearch(workspaceDir, query, opts = {}) {
|
|
|
1037
1111
|
'--allow-all-tools',
|
|
1038
1112
|
'--add-dir',
|
|
1039
1113
|
workspaceDir
|
|
1040
|
-
];
|
|
1114
|
+
]);
|
|
1041
1115
|
|
|
1042
|
-
const r = await run(
|
|
1116
|
+
const r = await run(spawnCmd, spawnArgs, workspaceDir);
|
|
1043
1117
|
const out = (r.stdout + r.stderr).trim();
|
|
1044
1118
|
if (r.code !== 0) return { ok: false, error: out || 'Copilot returned non-zero exit code.' };
|
|
1045
1119
|
|
|
@@ -4081,8 +4155,7 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4081
4155
|
// Build the system instructions (small, always fits in -p)
|
|
4082
4156
|
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
4157
|
|
|
4084
|
-
|
|
4085
|
-
const cmd = process.env.COPILOT_CMD || 'copilot';
|
|
4158
|
+
const copilotResolved = getCopilotCmd();
|
|
4086
4159
|
|
|
4087
4160
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4088
4161
|
const agentEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
@@ -4097,8 +4170,8 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4097
4170
|
|
|
4098
4171
|
try {
|
|
4099
4172
|
let r;
|
|
4100
|
-
const
|
|
4101
|
-
if (planImageDir)
|
|
4173
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4174
|
+
if (planImageDir) copilotExtra.push('--add-dir', planImageDir);
|
|
4102
4175
|
|
|
4103
4176
|
if (needsFile) {
|
|
4104
4177
|
// Write ONLY the user text to a temp file; keep instructions in -p
|
|
@@ -4112,15 +4185,18 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
4112
4185
|
const rulesTmpFile = path.join(os.tmpdir(), `freya-rules-${Date.now()}.txt`);
|
|
4113
4186
|
fs.writeFileSync(rulesTmpFile, rulesText, 'utf8');
|
|
4114
4187
|
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
|
-
|
|
4188
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4189
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', minPrompt, '--allow-all-tools']);
|
|
4190
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4117
4191
|
try { fs.unlinkSync(rulesTmpFile); } catch (_) { /* ignore */ }
|
|
4118
4192
|
} else {
|
|
4119
|
-
|
|
4120
|
-
|
|
4193
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4194
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', filePrompt, '--allow-all-tools']);
|
|
4195
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4121
4196
|
}
|
|
4122
4197
|
} else {
|
|
4123
|
-
|
|
4198
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, [...copilotExtra, '-p', fullPrompt, '--allow-all-tools']);
|
|
4199
|
+
r = await run(sc, sa, workspaceDir, agentEnv);
|
|
4124
4200
|
}
|
|
4125
4201
|
const out = (r.stdout + r.stderr).trim();
|
|
4126
4202
|
if (r.code !== 0) {
|
|
@@ -4614,17 +4690,17 @@ DADOS REAIS DO WORKSPACE (use estes dados para responder):
|
|
|
4614
4690
|
${dataContext}
|
|
4615
4691
|
${imageContext}`;
|
|
4616
4692
|
|
|
4617
|
-
const
|
|
4693
|
+
const copilotResolved = getCopilotCmd();
|
|
4618
4694
|
|
|
4619
4695
|
// BUG-48: pass FREYA_WORKSPACE_DIR so the Copilot subprocess uses correct DB
|
|
4620
4696
|
const oracleEnv = { FREYA_WORKSPACE_DIR: workspaceDir };
|
|
4621
4697
|
try {
|
|
4622
|
-
const
|
|
4698
|
+
const copilotExtra = ['-s', '--no-color', '--stream', 'off'];
|
|
4623
4699
|
// Allow Copilot to access the image file via its built-in tools
|
|
4624
4700
|
if (imagePath) {
|
|
4625
4701
|
const absImg = path.isAbsolute(imagePath) ? imagePath : path.join(workspaceDir, imagePath);
|
|
4626
4702
|
if (exists(absImg)) {
|
|
4627
|
-
|
|
4703
|
+
copilotExtra.push('--add-dir', path.dirname(absImg));
|
|
4628
4704
|
}
|
|
4629
4705
|
}
|
|
4630
4706
|
|
|
@@ -4637,12 +4713,14 @@ ${imageContext}`;
|
|
|
4637
4713
|
oracleTmpFile = path.join(os.tmpdir(), `freya-orchestrator-${Date.now()}.txt`);
|
|
4638
4714
|
fs.writeFileSync(oracleTmpFile, fullOraclePrompt, 'utf8');
|
|
4639
4715
|
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
|
-
|
|
4716
|
+
copilotExtra.push('--add-dir', os.tmpdir());
|
|
4717
|
+
copilotExtra.push('--allow-all-tools', '-p', filePrompt);
|
|
4718
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, copilotExtra);
|
|
4719
|
+
r = await run(sc, sa, workspaceDir, oracleEnv);
|
|
4643
4720
|
} else {
|
|
4644
|
-
|
|
4645
|
-
|
|
4721
|
+
copilotExtra.push('--allow-all-tools', '-p', fullOraclePrompt);
|
|
4722
|
+
const { cmd: sc, args: sa } = copilotSpawnArgs(copilotResolved, copilotExtra);
|
|
4723
|
+
r = await run(sc, sa, workspaceDir, oracleEnv);
|
|
4646
4724
|
}
|
|
4647
4725
|
if (oracleTmpFile) { try { fs.unlinkSync(oracleTmpFile); } catch (_) { /* ignore */ } }
|
|
4648
4726
|
const out = (r.stdout + r.stderr).trim();
|
package/package.json
CHANGED