primocode 8.10.2 → 8.10.3

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.
Files changed (3) hide show
  1. package/lib/act.js +65 -17
  2. package/lib/tools.js +13 -0
  3. package/package.json +1 -1
package/lib/act.js CHANGED
@@ -293,6 +293,14 @@ function encolherParaContinuar(messages) {
293
293
  }
294
294
  }
295
295
 
296
+ /** Quanto esperar quando o minuto estourou. O provedor costuma dizer
297
+ * ("Please try again in 7.664s"); sem isso, meio minuto é seguro. */
298
+ function esperaDoErro(e) {
299
+ const m = /try again in ([0-9.]+)s/i.exec(String((e && e.message) || e || ''));
300
+ const s = m ? Math.ceil(parseFloat(m[1])) + 2 : 30;
301
+ return Math.min(70, Math.max(5, s)) * 1000;
302
+ }
303
+
296
304
  function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou }) {
297
305
  if (falhasPendentes.length) {
298
306
  return `Parei sem concluir. A última falha foi: ${falhasPendentes[falhasPendentes.length - 1].texto}`;
@@ -367,6 +375,13 @@ async function runAct(userPrompt, opts) {
367
375
  // usuário — que não tem prompt nenhum para ajustar. É o modelo tropeçando
368
376
  // ao escrever o JSON da chamada, e quase sempre acerta na segunda.
369
377
  let funcoesQuebradas = 0;
378
+ // O teto de tokens é POR MINUTO, somando TODAS as chamadas — o plano, o
379
+ // resumo e cada rodada do loop dividem a mesma cota. Um pedido perfeito
380
+ // estoura se a rodada anterior já gastou o minuto. Encolher não resolve
381
+ // isso (num caso real "encolheu" de 2 para 2 mensagens); esperar a janela
382
+ // virar, sim.
383
+ let esperas = 0;
384
+ const MAX_ESPERAS = 3;
370
385
 
371
386
  let voltas = 0;
372
387
  for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
@@ -396,15 +411,30 @@ async function runAct(userPrompt, opts) {
396
411
  // Estourou o minuto: encolhe o que já foi feito e CONTINUA, em vez
397
412
  // de devolver erro e jogar fora o trabalho. O usuário não precisa
398
413
  // saber que isso aconteceu — a tarefa é dele, o encanamento é nosso.
399
- if (ehTetoDeToken(e) && compactacoes < MAX_COMPACTACOES && !PARAR) {
400
- compactacoes++;
401
- const antes = messages.length;
402
- encolherParaContinuar(messages);
403
- if (!subagent) {
404
- console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
414
+ if (ehTetoDeToken(e) && !PARAR) {
415
+ if (messages.length > 8 && compactacoes < MAX_COMPACTACOES) {
416
+ compactacoes++;
417
+ const antes = messages.length;
418
+ encolherParaContinuar(messages);
419
+ if (!subagent) {
420
+ console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
421
+ }
422
+ iteration--; // esta volta não contou: nada foi decidido
423
+ continue;
424
+ }
425
+ if (esperas < MAX_ESPERAS) {
426
+ esperas++;
427
+ const ms = esperaDoErro(e);
428
+ if (!subagent) {
429
+ const sp2 = new Spinner(`limite do minuto — continuando em ${Math.round(ms / 1000)}s`).start();
430
+ await new Promise((r) => setTimeout(r, ms));
431
+ sp2.stop();
432
+ } else {
433
+ await new Promise((r) => setTimeout(r, ms));
434
+ }
435
+ iteration--;
436
+ continue;
405
437
  }
406
- iteration--; // esta volta não contou: nada foi decidido
407
- continue;
408
438
  }
409
439
  const amigavel = friendlyError(e);
410
440
  if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
@@ -428,15 +458,33 @@ async function runAct(userPrompt, opts) {
428
458
  continue;
429
459
  }
430
460
 
431
- if (streamError && ehTetoDeToken(streamError) && compactacoes < MAX_COMPACTACOES && !PARAR) {
432
- compactacoes++;
433
- const antes = messages.length;
434
- encolherParaContinuar(messages);
435
- if (!subagent) {
436
- console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
461
+ if (streamError && ehTetoDeToken(streamError) && !PARAR) {
462
+ // Conversa longa: encolher resolve, porque o peso é o histórico.
463
+ if (messages.length > 8 && compactacoes < MAX_COMPACTACOES) {
464
+ compactacoes++;
465
+ const antes = messages.length;
466
+ encolherParaContinuar(messages);
467
+ if (!subagent) {
468
+ console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
469
+ }
470
+ iteration--;
471
+ continue;
472
+ }
473
+ // Conversa curta: o peso é o MINUTO, não o histórico — as chamadas
474
+ // anteriores gastaram a cota. Espera a janela virar e repete.
475
+ if (esperas < MAX_ESPERAS) {
476
+ esperas++;
477
+ const ms = esperaDoErro(streamError);
478
+ if (!subagent) {
479
+ const sp2 = new Spinner(`limite do minuto — continuando em ${Math.round(ms / 1000)}s`).start();
480
+ await new Promise((r) => setTimeout(r, ms));
481
+ sp2.stop();
482
+ } else {
483
+ await new Promise((r) => setTimeout(r, ms));
484
+ }
485
+ iteration--;
486
+ continue;
437
487
  }
438
- iteration--;
439
- continue;
440
488
  }
441
489
 
442
490
  if (streamError) {
@@ -648,5 +696,5 @@ async function runSubagent(args, ctx) {
648
696
  return resumo;
649
697
  }
650
698
 
651
- module.exports = { runAct, interromper, compactar, motivoDaParada, ehTetoDeToken,
699
+ module.exports = { runAct, interromper, compactar, motivoDaParada, ehTetoDeToken, esperaDoErro,
652
700
  anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
package/lib/tools.js CHANGED
@@ -379,6 +379,19 @@ async function despachar(name, args, ctx) {
379
379
  {
380
380
  switch (name) {
381
381
  case 'write_file': {
382
+ // Vídeo e áudio NÃO se escrevem com write_file. Num pedido
383
+ // real o agente criou um "video.mp4" de 43 bytes de texto e
384
+ // disse "vídeo criado com sucesso" — um arquivo falso, morto,
385
+ // entregue como resultado. O caminho certo existe e é dele:
386
+ // o Studio.
387
+ if (/\.(mp4|mov|avi|mkv|webm|mp3|wav|ogg|m4a|flac)$/i.test(String(args.path || ''))) {
388
+ return {
389
+ ok: false,
390
+ error: `"${args.path}" é arquivo de vídeo/áudio, e write_file só escreve TEXTO — isso criaria um arquivo falso que não abre. `
391
+ + 'Vídeo se faz no Studio: studio_criar com ferramenta "corte" (leia studio_spec antes). '
392
+ + 'O Studio devolve o link "ver" — é ESSE o vídeo. Não use ffmpeg: não é preciso.',
393
+ };
394
+ }
382
395
  const filePath = resolveInProject(args.path, projectDir);
383
396
  const content = args.content || '';
384
397
  const created = !fs.existsSync(filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.10.2",
3
+ "version": "8.10.3",
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": {