primocode 8.10.1 → 8.10.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/bin/primocode.js +26 -5
- package/lib/act.js +29 -2
- package/lib/studio.js +31 -4
- package/package.json +1 -1
package/bin/primocode.js
CHANGED
|
@@ -550,10 +550,19 @@ async function sendPrompt(userText) {
|
|
|
550
550
|
let jaFeito = '';
|
|
551
551
|
for (let i = 0; i < passos.length; i++) {
|
|
552
552
|
if (i > 0) console.log(c.dim(` ⎿ ${passos[i].titulo}`));
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
553
|
+
// Só fato de passo CONCLUÍDO entra aqui. Num pedido real o passo de
|
|
554
|
+
// pesquisa morreu e o texto "Parei sem concluir..." foi injetado
|
|
555
|
+
// como "use estes fatos" — e o passo seguinte obedeceu: criou o
|
|
556
|
+
// vídeo sem fato nenhum, proibido de pesquisar.
|
|
557
|
+
const contexto = !jaFeito ? ''
|
|
558
|
+
: `\n\nO QUE JÁ FOI FEITO ANTES (use estes fatos, não pesquise de novo):\n${jaFeito}`;
|
|
559
|
+
const semFatos = i > 0 && !jaFeito
|
|
560
|
+
? '\n\nATENÇÃO: o passo anterior não trouxe fatos. Trabalhe só com o que está no pedido. '
|
|
561
|
+
+ 'É PROIBIDO inventar dado de empresa, data, preço ou pessoa — na dúvida, não afirme.'
|
|
562
|
+
: '';
|
|
563
|
+
const pedido = passos[i].pedido + contexto + semFatos;
|
|
564
|
+
|
|
565
|
+
const rodarPasso = () => runAct(pedido, {
|
|
557
566
|
server: state.config.server,
|
|
558
567
|
model: state.config.model,
|
|
559
568
|
effort: state.config.effort,
|
|
@@ -568,7 +577,19 @@ async function sendPrompt(userText) {
|
|
|
568
577
|
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
569
578
|
motor: state.engine === 'claude-code' ? claudeEngine : api,
|
|
570
579
|
});
|
|
571
|
-
|
|
580
|
+
|
|
581
|
+
r = await rodarPasso();
|
|
582
|
+
// Passo que desistiu (modelo mudo, voltas esgotadas) ganha UMA
|
|
583
|
+
// segunda chance — costuma ser tropeço transitório do modelo
|
|
584
|
+
// grátis. Falhou de novo: o plano segue com o aviso de "sem
|
|
585
|
+
// fatos", nunca com o texto da falha fantasiado de resultado.
|
|
586
|
+
if (!r.parado && r.concluiu === false && passos.length > 1) {
|
|
587
|
+
console.log(c.dim(' ⎿ o passo não concluiu — tentando de novo'));
|
|
588
|
+
r = await rodarPasso();
|
|
589
|
+
}
|
|
590
|
+
jaFeito = (r.concluiu !== false)
|
|
591
|
+
? String((r.assistantMessage && r.assistantMessage.content) || '').slice(0, 1200)
|
|
592
|
+
: '';
|
|
572
593
|
if (r.parado) break; // Esc Esc no meio do plano para o plano inteiro
|
|
573
594
|
}
|
|
574
595
|
state.history.push({ role: 'user', content: userText });
|
package/lib/act.js
CHANGED
|
@@ -362,6 +362,12 @@ async function runAct(userPrompt, opts) {
|
|
|
362
362
|
let compactacoes = 0;
|
|
363
363
|
const MAX_COMPACTACOES = 2;
|
|
364
364
|
|
|
365
|
+
// O provedor recusa chamada de função malformada com um 400 que dizia
|
|
366
|
+
// "Failed to call a function. Please adjust your prompt." NA CARA do
|
|
367
|
+
// usuário — que não tem prompt nenhum para ajustar. É o modelo tropeçando
|
|
368
|
+
// ao escrever o JSON da chamada, e quase sempre acerta na segunda.
|
|
369
|
+
let funcoesQuebradas = 0;
|
|
370
|
+
|
|
365
371
|
let voltas = 0;
|
|
366
372
|
for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
|
|
367
373
|
voltas++;
|
|
@@ -409,6 +415,19 @@ async function runAct(userPrompt, opts) {
|
|
|
409
415
|
emVoo = null;
|
|
410
416
|
if (PARAR) break;
|
|
411
417
|
|
|
418
|
+
// Chamada de função malformada: o modelo errou a SINTAXE da chamada,
|
|
419
|
+
// não a tarefa. Repetir a mesma rodada quase sempre resolve — e sem
|
|
420
|
+
// isto o 400 cru do provedor ("Failed to call a function. Please
|
|
421
|
+
// adjust your prompt.") caía na tela de quem não tem prompt nenhum
|
|
422
|
+
// para ajustar.
|
|
423
|
+
if (streamError && /failed to call a function|failed_generation|tool_use_failed/i.test(String(streamError))
|
|
424
|
+
&& funcoesQuebradas < 2 && !PARAR) {
|
|
425
|
+
funcoesQuebradas++;
|
|
426
|
+
if (!subagent) console.log(ident(profundidade) + c.dim(' ⎿ o modelo tropeçou na chamada — tentando de novo'));
|
|
427
|
+
iteration--;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
412
431
|
if (streamError && ehTetoDeToken(streamError) && compactacoes < MAX_COMPACTACOES && !PARAR) {
|
|
413
432
|
compactacoes++;
|
|
414
433
|
const antes = messages.length;
|
|
@@ -422,9 +441,11 @@ async function runAct(userPrompt, opts) {
|
|
|
422
441
|
|
|
423
442
|
if (streamError) {
|
|
424
443
|
const amigavel = friendlyError(streamError);
|
|
425
|
-
if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
|
|
444
|
+
if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` }, concluiu: false };
|
|
426
445
|
console.log('\n' + c.err(amigavel));
|
|
427
|
-
|
|
446
|
+
// concluiu: false — o plano de passos não pode tratar um erro de
|
|
447
|
+
// provedor como resultado e passá-lo adiante como "fato".
|
|
448
|
+
return { assistantMessage: { role: 'assistant', content: amigavel }, concluiu: false };
|
|
428
449
|
}
|
|
429
450
|
|
|
430
451
|
// Sem tool_calls: o modelo respondeu e acabou.
|
|
@@ -561,6 +582,11 @@ async function runAct(userPrompt, opts) {
|
|
|
561
582
|
}
|
|
562
583
|
}
|
|
563
584
|
|
|
585
|
+
// O que o modelo entregou por vontade própria, ANTES dos textos de
|
|
586
|
+
// desistência. É esta a diferença que o plano de passos precisa: um passo
|
|
587
|
+
// que "parou sem concluir" não pode virar fato para o passo seguinte.
|
|
588
|
+
const concluiu = Boolean(finalText) && !PARAR;
|
|
589
|
+
|
|
564
590
|
if (PARAR && !finalText) {
|
|
565
591
|
finalText = 'Parado por você (Esc Esc). O que já foi feito, ficou feito.';
|
|
566
592
|
if (!subagent) console.log('\n' + c.warn('⏹ ' + finalText));
|
|
@@ -594,6 +620,7 @@ async function runAct(userPrompt, opts) {
|
|
|
594
620
|
// INTEIRO, não só o passo — senão o próximo passo começa logo depois
|
|
595
621
|
// de o usuário ter mandado parar.
|
|
596
622
|
parado: PARAR,
|
|
623
|
+
concluiu,
|
|
597
624
|
};
|
|
598
625
|
}
|
|
599
626
|
|
package/lib/studio.js
CHANGED
|
@@ -256,7 +256,26 @@ const manual = () => comStudio(async () => ({ ok: true, ...(await pedir('GET', '
|
|
|
256
256
|
const ferramentas = () => comStudio(async () => ({ ok: true, ferramentas: await pedir('GET', '/api/ferramentas') }));
|
|
257
257
|
const spec = (slug) => comStudio(async () => ({ ok: true, spec: await pedir('GET', `/api/ferramentas/${encodeURIComponent(slug)}`) }));
|
|
258
258
|
const listar = () => comStudio(async () => ({ ok: true, arquivos: await pedir('GET', '/api/arquivos') }));
|
|
259
|
-
|
|
259
|
+
// 404 de id é quase sempre id INVENTADO: num pedido real o agente criou o
|
|
260
|
+
// arquivo, recebeu "corte-founders-ea74b" de volta, e chamou studio_ler com
|
|
261
|
+
// "imersao-foulders-ai" — um id que ele imaginou. O erro seco ("não
|
|
262
|
+
// encontrado") não dizia isso, e ele insistia. O erro que ensina, diz.
|
|
263
|
+
async function comIdReal(id, fn) {
|
|
264
|
+
try {
|
|
265
|
+
return await fn();
|
|
266
|
+
} catch (e) {
|
|
267
|
+
if (!/HTTP 404/.test(String(e && e.message))) throw e;
|
|
268
|
+
let existem = [];
|
|
269
|
+
try { existem = (await pedir('GET', '/api/arquivos')).map((a) => a.id).slice(0, 8); } catch {}
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
error: `O id "${id}" não existe. NÃO invente id: use EXATAMENTE o campo "id" que studio_criar devolveu.`
|
|
273
|
+
+ (existem.length ? ` Os que existem agora: ${existem.join(', ')}.` : ''),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const ler = (id) => comStudio(() => comIdReal(id, async () => ({ ok: true, arquivo: await pedir('GET', `/api/arquivos/${encodeURIComponent(id)}`) })));
|
|
260
279
|
|
|
261
280
|
const criar = ({ ferramenta, titulo, doc }) => comStudio(async () => {
|
|
262
281
|
if (!ferramenta) return { ok: false, error: 'Diga a ferramenta: prisma, tela, prosa, grade, corte ou traco.' };
|
|
@@ -265,14 +284,14 @@ const criar = ({ ferramenta, titulo, doc }) => comStudio(async () => {
|
|
|
265
284
|
return { ok: true, ...r };
|
|
266
285
|
});
|
|
267
286
|
|
|
268
|
-
const atualizar = ({ id, titulo, doc }) => comStudio(async () => {
|
|
287
|
+
const atualizar = ({ id, titulo, doc }) => comStudio(() => comIdReal(id, async () => {
|
|
269
288
|
if (!id) return { ok: false, error: 'Falta o id do arquivo.' };
|
|
270
289
|
const corpo = {};
|
|
271
290
|
if (titulo != null) corpo.titulo = titulo;
|
|
272
291
|
if (doc != null) corpo.doc = doc;
|
|
273
292
|
const r = await pedir('PUT', `/api/arquivos/${encodeURIComponent(id)}`, corpo);
|
|
274
293
|
return { ok: true, ...r };
|
|
275
|
-
});
|
|
294
|
+
}));
|
|
276
295
|
|
|
277
296
|
// ── Mídia: a imagem de fundo e a música do usuário ───────────────────────
|
|
278
297
|
// Sem isto o agente não tinha como pôr foto de fundo nem trilha própria: a
|
|
@@ -288,7 +307,15 @@ const midiaListar = () => comStudio(async () => ({ ok: true, midia: await pedir(
|
|
|
288
307
|
const midiaEnviar = ({ arquivo, nome }) => comStudio(async () => {
|
|
289
308
|
if (!arquivo) return { ok: false, error: 'Diga o caminho do arquivo (arquivo: "foto.jpg").' };
|
|
290
309
|
const caminho = path.resolve(arquivo);
|
|
291
|
-
if (!fs.existsSync(caminho))
|
|
310
|
+
if (!fs.existsSync(caminho)) {
|
|
311
|
+
return {
|
|
312
|
+
ok: false,
|
|
313
|
+
error: `Arquivo não existe: ${arquivo}. NÃO invente arquivo de mídia — num pedido real o agente `
|
|
314
|
+
+ 'tentou fundo.jpg, trilha.mp3 e logo.png que nunca existiram. Sem arquivo do usuário, siga sem mídia: '
|
|
315
|
+
+ 'fundo pode ser "aurora"/"raios"/"brilho" (gerados), e trilha pode ser gerada — '
|
|
316
|
+
+ '"trilha":{"tipo":"gerada","estilo":"epico"}. O vídeo fica pronto do mesmo jeito.',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
292
319
|
|
|
293
320
|
const ext = path.extname(caminho).toLowerCase();
|
|
294
321
|
if (!TIPOS_MIDIA[ext]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.10.
|
|
3
|
+
"version": "8.10.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": {
|