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 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
- const pedido = i === 0 ? passos[i].pedido
554
- : `${passos[i].pedido}\n\nO QUE JÁ FOI FEITO ANTES (use estes fatos, não pesquise de novo):\n${jaFeito}`;
555
-
556
- r = await runAct(pedido, {
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
- jaFeito = String((r.assistantMessage && r.assistantMessage.content) || '').slice(0, 1200);
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
- return { assistantMessage: { role: 'assistant', content: amigavel } };
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
- const ler = (id) => comStudio(async () => ({ ok: true, arquivo: await pedir('GET', `/api/arquivos/${encodeURIComponent(id)}`) }));
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)) return { ok: false, error: `Arquivo não existe: ${arquivo}` };
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.1",
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": {