primocode 8.2.0 → 8.2.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.
- package/lib/act.js +57 -9
- package/lib/claude-engine.js +57 -10
- package/lib/referencia.js +87 -7
- package/lib/studio.js +33 -4
- package/package.json +2 -2
package/lib/act.js
CHANGED
|
@@ -160,6 +160,37 @@ function summarizeForModel(result) {
|
|
|
160
160
|
return json.length > 6000 ? json.slice(0, 6000) + '…(truncado)' : json;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Quantos resultados de tool ficam INTEIROS na conversa. Os mais novos são o
|
|
164
|
+
// que o modelo precisa para decidir o próximo passo; os velhos viram peso.
|
|
165
|
+
const RESULTADOS_INTEIROS = 3;
|
|
166
|
+
const RESTO_DO_RESULTADO = 300;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Encolhe os resultados de tool ANTIGOS antes de mandar a conversa.
|
|
170
|
+
*
|
|
171
|
+
* Sem isto, cada página lida e cada pesquisa ficava inteira no histórico para
|
|
172
|
+
* sempre. Medido num pedido real ("pesquise sobre a empresa X e faça um
|
|
173
|
+
* vídeo"): cinco pesquisas e seis leituras de página levaram a conversa a
|
|
174
|
+
* 27.454 tokens contra um teto de 6.000 por minuto do plano grátis — a tarefa
|
|
175
|
+
* morreu no meio, depois de todo o trabalho de pesquisa já feito.
|
|
176
|
+
*
|
|
177
|
+
* Nenhuma mensagem é REMOVIDA: a API exige que cada role:'tool' continue
|
|
178
|
+
* pareada com o tool_call que a pediu. Só o texto encolhe.
|
|
179
|
+
*/
|
|
180
|
+
function compactar(messages) {
|
|
181
|
+
const indices = [];
|
|
182
|
+
for (let i = 0; i < messages.length; i++) if (messages[i].role === 'tool') indices.push(i);
|
|
183
|
+
if (indices.length <= RESULTADOS_INTEIROS) return messages;
|
|
184
|
+
|
|
185
|
+
const encolher = new Set(indices.slice(0, -RESULTADOS_INTEIROS));
|
|
186
|
+
return messages.map((m, i) => {
|
|
187
|
+
if (!encolher.has(i)) return m;
|
|
188
|
+
const texto = String(m.content || '');
|
|
189
|
+
if (texto.length <= RESTO_DO_RESULTADO) return m;
|
|
190
|
+
return { ...m, content: texto.slice(0, RESTO_DO_RESULTADO) + `…(resultado antigo encurtado; eram ${texto.length} caracteres)` };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
163
194
|
function buildMemoryContext(projectDir, projectName) {
|
|
164
195
|
const parts = [];
|
|
165
196
|
const geral = cfg.readGeneralMemory();
|
|
@@ -210,7 +241,7 @@ async function runAct(userPrompt, opts) {
|
|
|
210
241
|
let streaming = false;
|
|
211
242
|
|
|
212
243
|
try {
|
|
213
|
-
await motor.requestAct(server, { messages, model, effort, token, projectDir, projectName, subagent }, (evt) => {
|
|
244
|
+
await motor.requestAct(server, { messages: compactar(messages), model, effort, token, projectDir, projectName, subagent }, (evt) => {
|
|
214
245
|
if (evt.erro) { streamError = evt.erro; return; }
|
|
215
246
|
if (evt.tool_call) { toolCalls.push(evt.tool_call); return; }
|
|
216
247
|
if (evt.delta) {
|
|
@@ -271,7 +302,7 @@ async function runAct(userPrompt, opts) {
|
|
|
271
302
|
messages.push({
|
|
272
303
|
role: 'tool',
|
|
273
304
|
tool_call_id: tc.id,
|
|
274
|
-
content: `NÃO terminou. Estas tools falharam e você não corrigiu: ${falhasPendentes.join('; ')}. `
|
|
305
|
+
content: `NÃO terminou. Estas tools falharam e você não corrigiu: ${falhasPendentes.map((f) => f.texto).join('; ')}. `
|
|
275
306
|
+ 'Conserte agora (leia o arquivo antes de editar) ou, se for impossível, diga claramente ao usuário o que falhou.',
|
|
276
307
|
});
|
|
277
308
|
continue;
|
|
@@ -306,12 +337,29 @@ async function runAct(userPrompt, opts) {
|
|
|
306
337
|
// a lista inteira e o agente conseguia dizer "concluído" com uma
|
|
307
338
|
// falha real ainda pendente.
|
|
308
339
|
if (result && result.ok === false) {
|
|
309
|
-
falhasPendentes.push(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
340
|
+
falhasPendentes.push({
|
|
341
|
+
tool: tc.name,
|
|
342
|
+
alvo: tc.arguments?.path || '',
|
|
343
|
+
texto: `${tc.name}(${tc.arguments?.path || ''}): ${result.error}`,
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
// Deu certo: some com o que ESTA tool tinha deixado pendente.
|
|
347
|
+
//
|
|
348
|
+
// Antes só write_file/edit_file limpavam, e por caminho. Uma
|
|
349
|
+
// falha já superada continuava bloqueando o finish para sempre:
|
|
350
|
+
// medido num pedido real, o browser_read errou o seletor
|
|
351
|
+
// ("main"), acertou na tentativa seguinte lendo 5.240 chars — e
|
|
352
|
+
// mesmo assim a tarefa morreu com "Parei sem concluir. A última
|
|
353
|
+
// falha foi: browser_read()". Acertar depois de errar é
|
|
354
|
+
// exatamente o que esta guarda quer premiar, não punir.
|
|
355
|
+
const alvo = tc.arguments?.path;
|
|
356
|
+
const sobrou = falhasPendentes.filter((f) => {
|
|
357
|
+
if (f.tool === tc.name) return false; // a mesma tool funcionou
|
|
358
|
+
if (alvo && f.alvo === alvo) return false; // o mesmo arquivo foi resolvido
|
|
359
|
+
return true;
|
|
360
|
+
});
|
|
313
361
|
falhasPendentes.length = 0;
|
|
314
|
-
falhasPendentes.push(...
|
|
362
|
+
falhasPendentes.push(...sobrou);
|
|
315
363
|
}
|
|
316
364
|
|
|
317
365
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: summarizeForModel(result) });
|
|
@@ -335,7 +383,7 @@ async function runAct(userPrompt, opts) {
|
|
|
335
383
|
// depois de a primeira tentativa de seletor falhar.
|
|
336
384
|
if (!finalText) {
|
|
337
385
|
finalText = falhasPendentes.length
|
|
338
|
-
? `Parei sem concluir. A última falha foi: ${falhasPendentes[falhasPendentes.length - 1]}`
|
|
386
|
+
? `Parei sem concluir. A última falha foi: ${falhasPendentes[falhasPendentes.length - 1].texto}`
|
|
339
387
|
: 'Parei sem concluir e sem explicação do modelo. Tente pedir de novo, em partes menores.';
|
|
340
388
|
if (!subagent) console.log('\n' + c.warn(finalText));
|
|
341
389
|
}
|
|
@@ -382,4 +430,4 @@ async function runSubagent(args, ctx) {
|
|
|
382
430
|
return resumo;
|
|
383
431
|
}
|
|
384
432
|
|
|
385
|
-
module.exports = { runAct, interromper };
|
|
433
|
+
module.exports = { runAct, interromper, compactar };
|
package/lib/claude-engine.js
CHANGED
|
@@ -24,16 +24,55 @@ const MARCA_FIM = '<<<FIM>>>';
|
|
|
24
24
|
*/
|
|
25
25
|
function extrairDecisao(texto) {
|
|
26
26
|
const t = String(texto || '');
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
|
|
28
|
+
// Varre TODOS os blocos completos e fica com o último que dá para ler.
|
|
29
|
+
//
|
|
30
|
+
// A versão anterior fazia lastIndexOf(INICIO) e depois procurava o FIM
|
|
31
|
+
// para frente. Bastava o modelo escrever a marca de abertura mais uma vez
|
|
32
|
+
// — explicando o formato, ou começando um bloco e desistindo — para o FIM
|
|
33
|
+
// não existir depois dela: a extração devolvia null e o protocolo CRU ia
|
|
34
|
+
// parar na tela do usuário, com tool_call e tudo. Foi o que aconteceu num
|
|
35
|
+
// pedido real de vídeo.
|
|
36
|
+
const blocos = [];
|
|
37
|
+
let i = 0;
|
|
38
|
+
while (true) {
|
|
39
|
+
const inicio = t.indexOf(MARCA_INICIO, i);
|
|
40
|
+
if (inicio === -1) break;
|
|
41
|
+
const fim = t.indexOf(MARCA_FIM, inicio + MARCA_INICIO.length);
|
|
42
|
+
if (fim === -1) break; // bloco aberto e não fechado: ignora
|
|
43
|
+
blocos.push(t.slice(inicio + MARCA_INICIO.length, fim));
|
|
44
|
+
i = fim + MARCA_FIM.length;
|
|
36
45
|
}
|
|
46
|
+
|
|
47
|
+
for (let k = blocos.length - 1; k >= 0; k--) {
|
|
48
|
+
const lido = lerJSON(blocos[k]);
|
|
49
|
+
if (lido) return lido;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** O JSON do bloco, tolerando o que o modelo costuma acrescentar sem querer. */
|
|
55
|
+
function lerJSON(bruto) {
|
|
56
|
+
let s = String(bruto || '').trim();
|
|
57
|
+
// Cerca de markdown em volta do JSON — o modelo põe por hábito.
|
|
58
|
+
s = s.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/, '').trim();
|
|
59
|
+
try { return JSON.parse(s); } catch {}
|
|
60
|
+
// Sobrou texto depois do objeto? Corta no fecha-chaves que casa com o primeiro.
|
|
61
|
+
const abre = s.indexOf('{');
|
|
62
|
+
if (abre === -1) return null;
|
|
63
|
+
let nivel = 0, dentro = false, escapa = false;
|
|
64
|
+
for (let k = abre; k < s.length; k++) {
|
|
65
|
+
const c = s[k];
|
|
66
|
+
if (escapa) { escapa = false; continue; }
|
|
67
|
+
if (c === '\\') { escapa = true; continue; }
|
|
68
|
+
if (c === '"') { dentro = !dentro; continue; }
|
|
69
|
+
if (dentro) continue;
|
|
70
|
+
if (c === '{') nivel++;
|
|
71
|
+
else if (c === '}' && --nivel === 0) {
|
|
72
|
+
try { return JSON.parse(s.slice(abre, k + 1)); } catch { return null; }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
37
76
|
}
|
|
38
77
|
|
|
39
78
|
const catalogo = require('./catalogo');
|
|
@@ -229,7 +268,15 @@ function ultimaMensagem(messages) {
|
|
|
229
268
|
function emitirDecisao(textoLido, onEvent) {
|
|
230
269
|
const decisao = extrairDecisao(textoLido);
|
|
231
270
|
if (!decisao) {
|
|
232
|
-
|
|
271
|
+
// Resposta sem bloco legível. Se ela AINDA ASSIM traz a marca do
|
|
272
|
+
// protocolo, o motor falhou: mostrar isso cru — com "tool_call" e
|
|
273
|
+
// JSON na cara do usuário — é pior do que dizer que falhou.
|
|
274
|
+
let conteudo = String(textoLido || '').trim();
|
|
275
|
+
if (conteudo.includes(MARCA_INICIO) || conteudo.includes(MARCA_FIM)) {
|
|
276
|
+
conteudo = conteudo.split(MARCA_INICIO)[0].trim()
|
|
277
|
+
|| 'O motor Claude Code devolveu uma decisão que não consegui ler. Tente de novo, ou volte ao motor padrão com /claude off.';
|
|
278
|
+
}
|
|
279
|
+
onEvent({ assistant_message: { content: conteudo } });
|
|
233
280
|
return;
|
|
234
281
|
}
|
|
235
282
|
if (decisao.tool_call) {
|
package/lib/referencia.js
CHANGED
|
@@ -102,10 +102,17 @@ function pegar(url, { timeout = TIMEOUT_MS, saltos = 0 } = {}) {
|
|
|
102
102
|
if (hostBloqueado(alvo.hostname)) return resolve(null);
|
|
103
103
|
const cliente = alvo.protocol === 'http:' ? http : https;
|
|
104
104
|
|
|
105
|
+
// O User-Agent precisa parecer navegador. Buscador nenhum responde a
|
|
106
|
+
// agente desconhecido: DuckDuckGo, Bing e Searx devolviam página VAZIA
|
|
107
|
+
// para o 'PrimoCode/1.0' — e o usuário via "nada encontrado (sem
|
|
108
|
+
// internet?)" com a internet funcionando perfeitamente. Site comum não
|
|
109
|
+
// liga para isso, e por isso as páginas abriam: só a BUSCA morria.
|
|
105
110
|
const opcoes = {
|
|
106
111
|
headers: {
|
|
107
|
-
'User-Agent': '
|
|
108
|
-
|
|
112
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 '
|
|
113
|
+
+ '(KHTML, like Gecko) Chrome/122.0 Safari/537.36',
|
|
114
|
+
Accept: 'text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8',
|
|
115
|
+
'Accept-Language': 'pt-BR,pt;q=0.9,en;q=0.8',
|
|
109
116
|
},
|
|
110
117
|
timeout,
|
|
111
118
|
};
|
|
@@ -191,6 +198,59 @@ async function buscarDDG(consulta, limite) {
|
|
|
191
198
|
return limparLinks(pares, limite);
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Wikipedia — a fonte que NÃO bloqueia.
|
|
203
|
+
*
|
|
204
|
+
* Buscador raspado por HTML (DDG, Bing, Searx) recusa quem não parece gente:
|
|
205
|
+
* devolve 202/302 e página vazia, e o usuário lê "nada encontrado (sem
|
|
206
|
+
* internet?)" com a internet ótima. Medido: no IP deste servidor os três
|
|
207
|
+
* calaram juntos, enquanto a API da Wikipedia respondeu 200 na hora.
|
|
208
|
+
*
|
|
209
|
+
* É API pública e estável, feita para ser consumida por programa — e para
|
|
210
|
+
* empresa, pessoa, cidade e evento costuma ser a MELHOR fonte, não a pior.
|
|
211
|
+
*/
|
|
212
|
+
async function buscarWikipedia(consulta, limite) {
|
|
213
|
+
const achados = [];
|
|
214
|
+
for (const lingua of ['pt', 'en']) {
|
|
215
|
+
if (achados.length >= limite) break;
|
|
216
|
+
const url = `https://${lingua}.wikipedia.org/w/api.php?action=query&list=search`
|
|
217
|
+
+ `&srsearch=${encodeURIComponent(consulta)}&format=json&srlimit=${limite}`;
|
|
218
|
+
const bruto = await pegar(url);
|
|
219
|
+
if (!bruto) continue;
|
|
220
|
+
let j;
|
|
221
|
+
try { j = JSON.parse(bruto); } catch { continue; }
|
|
222
|
+
for (const r of (j.query && j.query.search) || []) {
|
|
223
|
+
achados.push({
|
|
224
|
+
titulo: r.title,
|
|
225
|
+
url: `https://${lingua}.wikipedia.org/wiki/${encodeURIComponent(r.title.replace(/ /g, '_'))}`,
|
|
226
|
+
nota: 'Wikipédia',
|
|
227
|
+
});
|
|
228
|
+
if (achados.length >= limite) break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return achados;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A resposta direta do DuckDuckGo (API oficial, sem raspagem).
|
|
236
|
+
*
|
|
237
|
+
* Não é busca de links: é o resumo que ele já tem sobre o assunto, mais os
|
|
238
|
+
* tópicos relacionados. Responde 200 onde a página de busca devolve 202.
|
|
239
|
+
*/
|
|
240
|
+
async function buscarRespostaDDG(consulta, limite) {
|
|
241
|
+
const bruto = await pegar(`https://api.duckduckgo.com/?q=${encodeURIComponent(consulta)}&format=json&no_html=1&skip_disambig=1`);
|
|
242
|
+
if (!bruto) return [];
|
|
243
|
+
let j;
|
|
244
|
+
try { j = JSON.parse(bruto); } catch { return []; }
|
|
245
|
+
const achados = [];
|
|
246
|
+
if (j.AbstractURL) achados.push({ titulo: j.Heading || consulta, url: j.AbstractURL, nota: j.AbstractSource });
|
|
247
|
+
for (const t of j.RelatedTopics || []) {
|
|
248
|
+
if (achados.length >= limite) break;
|
|
249
|
+
if (t.FirstURL) achados.push({ titulo: (t.Text || '').slice(0, 120), url: t.FirstURL });
|
|
250
|
+
}
|
|
251
|
+
return limparLinks(achados, limite);
|
|
252
|
+
}
|
|
253
|
+
|
|
194
254
|
/** Bing (HTML) — a reserva quando o DDG não responde. */
|
|
195
255
|
async function buscarBing(consulta, limite) {
|
|
196
256
|
const html = await pegar(`https://www.bing.com/search?q=${encodeURIComponent(consulta)}&setlang=en`);
|
|
@@ -218,13 +278,24 @@ async function buscarSearx(consulta, limite) {
|
|
|
218
278
|
* pesquisa inteira. Sem chave de API em nenhuma delas.
|
|
219
279
|
*/
|
|
220
280
|
async function buscar(consulta, limite = MAX_POR_FONTE) {
|
|
221
|
-
|
|
281
|
+
// Ordem deliberada: primeiro os buscadores raspados (dão os links mais
|
|
282
|
+
// frescos, quando respondem), depois as APIs que NUNCA bloqueiam. Antes a
|
|
283
|
+
// fila acabava no Searx e, com os três calados, a pesquisa inteira voltava
|
|
284
|
+
// vazia — mesmo havendo resposta boa na Wikipédia.
|
|
285
|
+
const portas = [buscarDDG, buscarBing, buscarSearx, buscarRespostaDDG, buscarWikipedia];
|
|
286
|
+
const juntos = [];
|
|
287
|
+
for (const porta of portas) {
|
|
222
288
|
try {
|
|
223
289
|
const achados = await porta(consulta, limite);
|
|
224
|
-
|
|
290
|
+
for (const a of achados) {
|
|
291
|
+
if (!juntos.some((j) => j.url === a.url)) juntos.push(a);
|
|
292
|
+
}
|
|
293
|
+
// Os raspados, quando respondem, já bastam. As APIs entram para
|
|
294
|
+
// completar quando eles vieram vazios.
|
|
295
|
+
if (juntos.length >= limite) break;
|
|
225
296
|
} catch {}
|
|
226
297
|
}
|
|
227
|
-
return
|
|
298
|
+
return juntos.slice(0, limite);
|
|
228
299
|
}
|
|
229
300
|
|
|
230
301
|
/** Respostas do StackOverflow — a API pública, sem chave. */
|
|
@@ -284,7 +355,16 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
284
355
|
const candidatos = [...web, ...so, ...docsConhecidas(termo)]
|
|
285
356
|
.slice(0, Math.max(2, Math.min(8, paginas + 4)));
|
|
286
357
|
if (!candidatos.length) {
|
|
287
|
-
return {
|
|
358
|
+
return {
|
|
359
|
+
ok: false,
|
|
360
|
+
error: `Nenhuma fonte respondeu sobre "${termo}".`,
|
|
361
|
+
consulta: termo,
|
|
362
|
+
// "sem internet?" era o palpite errado, e mandava o usuário conferir
|
|
363
|
+
// a conexão dele: medido, os buscadores devolvem 202/302 e página
|
|
364
|
+
// vazia para quem não parece navegador, com a rede perfeita.
|
|
365
|
+
provavelCausa: 'Os buscadores costumam recusar chamadas automáticas (bloqueio ou limite por excesso de buscas seguidas), e não a sua conexão.',
|
|
366
|
+
comoCorrigir: 'Tente OUTRA consulta, mais curta e específica. Se souber o site do assunto, vá direto nele com browser_open + browser_read — é o caminho mais confiável para fato de empresa.',
|
|
367
|
+
};
|
|
288
368
|
}
|
|
289
369
|
|
|
290
370
|
const lidas = [];
|
|
@@ -316,4 +396,4 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
316
396
|
};
|
|
317
397
|
}
|
|
318
398
|
|
|
319
|
-
module.exports = { pesquisar, buscar, stackoverflow, textoDe };
|
|
399
|
+
module.exports = { pesquisar, buscar, stackoverflow, textoDe, buscarWikipedia, buscarRespostaDDG };
|
package/lib/studio.js
CHANGED
|
@@ -41,8 +41,31 @@ const BASE = `http://127.0.0.1:${PORTA}`;
|
|
|
41
41
|
// criou) nunca são tocados.
|
|
42
42
|
const COPIAVEIS = new Set(['server.py', 'web', 'ferramentas', 'README.md', 'PRIMOCODE.md']);
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Onde está o Python 3 desta máquina.
|
|
46
|
+
*
|
|
47
|
+
* Procurar só por `python3` no PATH não basta, e isso custou uma tarefa
|
|
48
|
+
* inteira num Mac: o comando existe mas é o atalho da Apple, que sem as
|
|
49
|
+
* Ferramentas de Linha de Comando instaladas sai com erro em vez de versão —
|
|
50
|
+
* e o usuário lia "Python 3 não está instalado" com o Python instalado pelo
|
|
51
|
+
* Homebrew ali do lado, fora do PATH daquele terminal.
|
|
52
|
+
*
|
|
53
|
+
* Então: nomes comuns, versões numeradas, e os caminhos absolutos onde o
|
|
54
|
+
* Python realmente mora em cada sistema.
|
|
55
|
+
*/
|
|
44
56
|
function pythonCmd() {
|
|
45
|
-
const
|
|
57
|
+
const numeradas = ['python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3.9'];
|
|
58
|
+
let candidatos;
|
|
59
|
+
if (os.platform() === 'win32') {
|
|
60
|
+
candidatos = ['python', 'py', 'python3', ...numeradas];
|
|
61
|
+
} else {
|
|
62
|
+
candidatos = [
|
|
63
|
+
'python3', 'python', ...numeradas,
|
|
64
|
+
// Homebrew (Apple Silicon e Intel) e o do sistema — o PATH de um
|
|
65
|
+
// terminal aberto por atalho às vezes não tem nenhum dos dois.
|
|
66
|
+
'/opt/homebrew/bin/python3', '/usr/local/bin/python3', '/usr/bin/python3',
|
|
67
|
+
];
|
|
68
|
+
}
|
|
46
69
|
for (const c of candidatos) {
|
|
47
70
|
try {
|
|
48
71
|
const v = execSync(`${c} --version`, { stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000 }).toString();
|
|
@@ -130,10 +153,16 @@ async function garantirNoAr() {
|
|
|
130
153
|
if (!py) {
|
|
131
154
|
return {
|
|
132
155
|
ok: false,
|
|
133
|
-
error: 'O Studio precisa do Python 3,
|
|
156
|
+
error: 'O Studio precisa do Python 3, e não achei nenhum nesta máquina.',
|
|
134
157
|
comoResolver: os.platform() === 'darwin'
|
|
135
|
-
? '
|
|
136
|
-
|
|
158
|
+
? 'No Mac, o mais rápido é: xcode-select --install (traz o Python 3 da Apple). '
|
|
159
|
+
+ 'Ou, se você usa Homebrew: brew install python. Depois é só pedir de novo.'
|
|
160
|
+
: os.platform() === 'win32'
|
|
161
|
+
? 'Instale em python.org/downloads (marque "Add python.exe to PATH" no instalador).'
|
|
162
|
+
: 'Instale o Python 3 pelo gerenciador da sua distribuição (ex: sudo apt install python3).',
|
|
163
|
+
// Sem isto o agente responde "não consegui" e para. O trabalho
|
|
164
|
+
// pedido quase sempre tem um caminho que não depende do Studio.
|
|
165
|
+
enquantoIsso: 'Não desista da tarefa: dá para entregar em HTML com write_file e abrir com open_in_browser.',
|
|
137
166
|
};
|
|
138
167
|
}
|
|
139
168
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.2",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|