primocode 10.0.0 → 10.0.9

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 (55) hide show
  1. package/README.md +2 -2
  2. package/bin/primocode.js +69 -1
  3. package/lib/act.js +118 -19
  4. package/lib/browser.js +89 -5
  5. package/lib/catalogo.js +1 -1
  6. package/lib/claude-engine.js +38 -17
  7. package/lib/codex-engine.js +27 -9
  8. package/lib/entrada.js +73 -9
  9. package/lib/estudio/cinema.js +27 -9
  10. package/lib/estudio/diretor.js +13 -1
  11. package/lib/estudio/ende.js +16 -5
  12. package/lib/estudio/estilo.js +23 -8
  13. package/lib/estudio/gerativo.js +1453 -0
  14. package/lib/estudio/hibrido.js +165 -0
  15. package/lib/estudio/index.js +244 -36
  16. package/lib/estudio/kit/gerativo.jsx +980 -0
  17. package/lib/estudio/kit/index.jsx +3 -1
  18. package/lib/estudio/kit/tres.jsx +308 -0
  19. package/lib/estudio/kit.js +7 -1
  20. package/lib/estudio/lancamento.js +28 -0
  21. package/lib/estudio/referencias.js +105 -0
  22. package/lib/estudio/remotion.js +53 -9
  23. package/lib/estudio/ritmo.js +2 -2
  24. package/lib/estudio/sons-leves.js +38 -1
  25. package/lib/estudio/variedade.js +105 -0
  26. package/lib/estudio/vox.js +37 -8
  27. package/lib/mcp.js +156 -0
  28. package/lib/personas.js +67 -0
  29. package/lib/skills.js +36 -0
  30. package/lib/tools.js +22 -3
  31. package/lib/ui.js +41 -6
  32. package/lib/video/auto.js +329 -0
  33. package/lib/video/camadas.js +207 -0
  34. package/lib/video/cli.js +1 -1
  35. package/lib/video/filters.js +39 -6
  36. package/lib/video/fx3d/Camadas.jsx +64 -0
  37. package/lib/video/fx3d/Objetos.jsx +128 -0
  38. package/lib/video/fx3d/Root.jsx +18 -0
  39. package/lib/video/fx3d/modelos.jsx +304 -0
  40. package/lib/video/fx3d.js +108 -0
  41. package/lib/video/gerados.js +119 -0
  42. package/lib/video/objetos.js +111 -0
  43. package/lib/video/ops-fx.js +453 -0
  44. package/lib/video/ops.js +30 -5
  45. package/lib/video/paralelo.js +50 -0
  46. package/lib/video/pedido.js +191 -0
  47. package/lib/video/rastrear.js +95 -0
  48. package/lib/video/rastro.js +276 -0
  49. package/lib/video/render.js +67 -14
  50. package/lib/video/texto.js +6 -4
  51. package/lib/video/timeline.js +9 -1
  52. package/package.json +2 -2
  53. package/skills/motion-de-primeira/SKILL.md +5 -4
  54. package/studio/narrar.py +10 -7
  55. package/studio/rastrear.py +399 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v10.0.0
1
+ # PrimoCode v10.0.9
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v10.0.0
12
+ ▐▛███▜▌ Primo Code v10.0.9
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
package/bin/primocode.js CHANGED
@@ -62,6 +62,17 @@ const tempos = require('../lib/tempos');
62
62
  process.on('uncaughtException', (e) => {
63
63
  fala.problema(e);
64
64
  });
65
+ /* O Node sai com 0 quando o laço de eventos esvazia — e uma promessa que
66
+ nunca resolve, sem timer nem socket vivo, faz exatamente isso: o pedido
67
+ "termina" calado no meio (visto no vídeo de escopo fechado, 01/10). Com
68
+ tarefa em andamento, sair assim é bug: diz, e sai com erro. */
69
+ process.on('beforeExit', () => {
70
+ if (global.__primocodeTarefa) {
71
+ process.stderr.write(`\n ⚠ o PrimoCode ia sair no meio da tarefa (${global.__primocodeTarefa}). Diga "continua" para retomar.\n`);
72
+ process.exitCode = 3;
73
+ global.__primocodeTarefa = null;
74
+ }
75
+ });
65
76
  process.on('unhandledRejection', (e) => {
66
77
  fala.problema(e instanceof Error ? e : new Error(String(e)));
67
78
  });
@@ -639,6 +650,12 @@ function pedeProjetoNovo(texto) {
639
650
  const RE_COMPUTADOR = /\b(abr[ae]|abrir|clica|clique|clicar|digita|digite|digitar|mouse|cursor|teclado|meu computador|no (meu )?mac|na (minha )?tela|aplicativo|spotify|safari|finder|whatsapp|mensagens|m[úu]sica|configura[çc][õo]es|ajustes)\b/i;
640
651
  function querComputador(texto) { return RE_COMPUTADOR.test(String(texto || '')); }
641
652
 
653
+ /* Pedido de VER o navegador ("abre o youtube", "entra no site x", "testa no
654
+ Chrome"). Sem isto as ferramentas browser_* rodam escondidas: pesquisar e
655
+ conferir não precisam abrir aba na tela da pessoa (02/10). */
656
+ const RE_NAVEGADOR = /\b(navegador|chrome|browser|edge|brave|aba|abas|youtube|gmail|instagram|facebook|linkedin|login|loga|logar|(abr[ae]|abrir|entr[ae]|entrar|acess[ae]|acessar|mostr[ae]|testa|testar)\b[^.?!]{0,25}\b(site|p[áa]gina|link|url|google|navegador))\b/i;
657
+ function querNavegador(texto) { return RE_NAVEGADOR.test(String(texto || '')); }
658
+
642
659
  /* Guarda onde se está, para a próxima sessão continuar aqui. */
643
660
  function entrarNoProjeto(dir, origem = 'projeto') {
644
661
  state.workDir = dir;
@@ -1051,6 +1068,42 @@ async function enviarPedido(userText) {
1051
1068
 
1052
1069
  const plan = prepararPasta(userText);
1053
1070
 
1071
+ // O Chrome só aparece na tela quando o pedido é de navegador (lib/browser.js, definirVisivel).
1072
+ require('../lib/browser.js').definirVisivel(querNavegador(userText));
1073
+
1074
+ /* AS REFERÊNCIAS DO VÍDEO, decididas pela pessoa antes de começar
1075
+ (lib/estudio/referencias.js): web aberta, só o escopo que ela der,
1076
+ ou nenhuma — só motion. No pedido único não há quem responda, e
1077
+ vale o aberto. */
1078
+ const referencias = require('../lib/estudio/referencias.js');
1079
+ referencias.limpar();
1080
+ const producao = require('../lib/estudio/diretor.js').precisaDeProducao(userText);
1081
+ if (producao.producao && producao.formato === 'video') {
1082
+ /* O modo sai do próprio pedido quando ele diz ("só motion", "só do
1083
+ site x.com", "web aberta"). Quando NÃO diz, pergunta antes de
1084
+ começar — "vídeo" às vezes quer dizer só o motion (02/10). No
1085
+ pedido único não há quem responda, e vale o aberto. */
1086
+ const r = referencias.inferir(userText);
1087
+ if (!r.explicito && process.stdin.isTTY && !state.semTerminal) {
1088
+ console.log('');
1089
+ console.log(' ' + c.brand('▸') + ' ' + c.bold(c.white('Que vídeo você quer?')));
1090
+ console.log(' ' + c.dim('1') + c.muted(' só motion — animação pura, nenhuma foto nem vídeo de fora'));
1091
+ console.log(' ' + c.dim('2') + c.muted(' motion + web aberta — ele pesquisa e busca imagem e vídeo à vontade'));
1092
+ console.log(' ' + c.dim('3') + c.muted(' motion + escopo fechado — só os sites e termos que você disser'));
1093
+ const dito = String(await ask(' ' + c.brand('❯ ') + c.muted('1, 2 ou 3 '))).trim();
1094
+ // Aqui 1 é o motion (a ordem da pergunta), não o 1 = aberto do entender().
1095
+ const modo = { 1: 'sem', 2: 'aberto', 3: 'fechado' }[(/^\s*([123])\b/.exec(dito) || [])[1]] || referencias.entender(dito) || 'aberto';
1096
+ r.modo = modo;
1097
+ r.escopo = modo === 'fechado'
1098
+ ? String(await ask(' ' + c.brand('❯ ') + c.muted('quais sites ou termos? (separados por vírgula) '))).trim()
1099
+ : '';
1100
+ if (modo === 'fechado' && !r.escopo) r.modo = 'aberto';
1101
+ }
1102
+ referencias.definir(r.modo, r.escopo);
1103
+ console.log(c.dim(` ⎿ referências: ${{ aberto: 'web aberta', fechado: 'só ' + r.escopo, sem: 'nenhuma, só motion' }[r.modo]}`)
1104
+ + (r.modo === 'aberto' ? c.dim(' · diga "só motion" ou "só do site x.com" para mudar') : ''));
1105
+ }
1106
+
1054
1107
  // O resumo da rodada anterior é feito em segundo plano logo depois
1055
1108
  // dela; aqui só se espera ele terminar, para não sair com memória velha.
1056
1109
  //
@@ -1117,7 +1170,8 @@ async function enviarPedido(userText) {
1117
1170
  permissoes: state.config.permissoes,
1118
1171
  // Pedido de mexer no computador leva a dica que libera as
1119
1172
  // ferramentas desktop_* no servidor (ver DICA_COMPUTADOR).
1120
- instrucao: i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
1173
+ instrucao: [i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
1174
+ i === 0 ? require('../lib/estudio/referencias.js').instrucao() : ''].filter(Boolean).join('\n\n'),
1121
1175
  });
1122
1176
 
1123
1177
  r = await rodarPasso();
@@ -2569,6 +2623,13 @@ async function main() {
2569
2623
  if (process.argv[2] === 'video') {
2570
2624
  process.exit(await require('../lib/video/cli.js').main(process.argv.slice(3)));
2571
2625
  }
2626
+ // `primocode mcp …`: os servidores MCP pelo run_shell (lib/mcp.js).
2627
+ if (process.argv[2] === 'skill' || process.argv[2] === 'skills') {
2628
+ process.exit(require('../lib/skills.js').main(process.argv.slice(3)));
2629
+ }
2630
+ if (process.argv[2] === 'mcp') {
2631
+ process.exit(await require('../lib/mcp.js').main(process.argv.slice(3)));
2632
+ }
2572
2633
  cfg.ensureStructure();
2573
2634
 
2574
2635
  // Padrão fixo: ~/primocode, não importa de onde o comando foi dado. Antes ele
@@ -2626,7 +2687,9 @@ async function main() {
2626
2687
  runAct próprio, e por isso ficava sem tudo o que vive lá: o plano em
2627
2688
  passos, a retomada automática, o @arquivo, o checkpoint do /desfazer. */
2628
2689
  state.semTerminal = true;
2690
+ global.__primocodeTarefa = promptText.slice(0, 60);
2629
2691
  await sendPrompt(promptText);
2692
+ global.__primocodeTarefa = null;
2630
2693
  process.exit(0);
2631
2694
  }
2632
2695
 
@@ -2641,6 +2704,9 @@ async function main() {
2641
2704
  comandos: comandosDoMenu,
2642
2705
  vazio: 'peça alguma coisa, ou / para os comandos',
2643
2706
  });
2707
+ // O spinner desenha dentro da caixa (lib/entrada.js mostrarSpinner).
2708
+ if (rl.tty) Spinner.caixa = rl;
2709
+ ui.titulo('PrimoCode');
2644
2710
 
2645
2711
  rl.prompt();
2646
2712
 
@@ -2717,6 +2783,7 @@ async function main() {
2717
2783
  while (queue.length) {
2718
2784
  const text = queue.shift().trim();
2719
2785
  if (!text) continue;
2786
+ ui.titulo(`⠋ PrimoCode — ${text}`);
2720
2787
  try {
2721
2788
  if (ehBash(text)) await rodarBash(text);
2722
2789
  else if (isSlash(text)) await handleSlash(text);
@@ -2740,6 +2807,7 @@ async function main() {
2740
2807
  busy = false;
2741
2808
  state.trabalhando = false;
2742
2809
  if (rl.trabalhando) rl.trabalhando(false);
2810
+ ui.titulo('PrimoCode');
2743
2811
  // Se o stdin já fechou (pipe, Ctrl+D), a despedida espera a fila terminar
2744
2812
  // — senão ela aparece antes do resultado do último pedido.
2745
2813
  if (closed) sayBye();
package/lib/act.js CHANGED
@@ -31,6 +31,7 @@ const preLeitura = require('./pre-leitura');
31
31
  // volta a mais não é volta em círculo — é trabalho. Quinze acabavam no meio
32
32
  // de um vídeo de dez cenas com pesquisa, e a pessoa lia "gastei as voltas".
33
33
  const MAX_ITERATIONS = Number(process.env.PRIMOCODE_MAX_VOLTAS || 40);
34
+ const MAX_VOLTAS_AGENTE = Number(process.env.PRIMOCODE_MAX_VOLTAS_AGENTE || 20);
34
35
  // Quantas vezes o agente é EMPURRADO quando para no meio para narrar
35
36
  // ("agora vou criar o arquivo…") em vez de fazer. Ver `pareceParadaNoMeio`.
36
37
  const MAX_EMPURROES = 3;
@@ -39,6 +40,10 @@ const MAX_RODADAS_PARADAS = 3;
39
40
  const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
40
41
  // As leituras que, repetidas sem mudança no meio, devolvem o guardado.
41
42
  const RELEITURA = new Set(['read_file', 'list_dir']);
43
+ /* O que só lê: pode rodar junto com as outras leituras da mesma rodada. */
44
+ const SO_LEITURA = new Set(['read_file', 'list_dir', 'glob', 'grep', 'pesquisar_referencia', 'estudio_site']);
45
+ const ehLeitura = (tc) => SO_LEITURA.has(tc.name)
46
+ || (tc.name === 'run_shell' && /^\s*primocode\s+(skill|mcp\s+list|video\s+(help|get_timeline|get_transcript))\b/.test(String((tc.arguments && tc.arguments.command) || '')));
42
47
 
43
48
  // ── Interrupção (Esc Esc) ─────────────────────────────────────────────────
44
49
  // O usuário aperta Esc duas vezes e o agente PARA — entre uma rodada e outra
@@ -526,7 +531,7 @@ const TROCA_DE_ROTA = 'Você ficou três voltas sem conseguir rodar nada, repeti
526
531
  + '(write_todos) e faça AGORA o primeiro passo, por um caminho diferente: outra ferramenta, outro arquivo, '
527
532
  + 'outro comando. Se não houver caminho, chame finish e diga em uma frase o que impediu.';
528
533
 
529
- function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou }) {
534
+ function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou, teto = MAX_ITERATIONS }) {
530
535
  if (falhasPendentes.length) {
531
536
  return `Parei sem concluir. A última falha foi: ${falhasPendentes[falhasPendentes.length - 1].texto}`;
532
537
  }
@@ -534,11 +539,11 @@ function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou }) {
534
539
  return 'Fiquei preso: tentei dois caminhos e os dois voltaram ao mesmo lugar, então parei antes de gastar sua cota à toa. '
535
540
  + 'O que já fiz está salvo. Diga "continua" que eu retomo daqui.';
536
541
  }
537
- if (voltas >= MAX_ITERATIONS) {
542
+ if (voltas >= teto) {
538
543
  const emFalso = voltasEmFalso
539
544
  ? ` ${voltasEmFalso} ${voltasEmFalso === 1 ? 'chamada foi repetida' : 'chamadas foram repetidas'} e não rodaram de novo.`
540
545
  : '';
541
- return `Usei as ${MAX_ITERATIONS} voltas deste pedido sem chegar ao fim.${emFalso}`
546
+ return `Usei as ${teto} voltas deste pedido sem chegar ao fim.${emFalso}`
542
547
  + ' O que já fiz está salvo. Diga "continua" que eu retomo daqui.';
543
548
  }
544
549
  return 'Parei sem concluir e sem explicação do modelo. Diga "continua" que eu tento de novo daqui.';
@@ -632,7 +637,18 @@ const RITMO = '[ritmo]\n'
632
637
  + '- O resultado de write_file/edit_file é a verdade. Não releia um arquivo que você acabou de escrever para conferir (nem com cat/sed/head): a conferência é do PrimoCode.\n'
633
638
  + '- Várias ações independentes vão na MESMA resposta (ex.: escrever index.html, style.css e script.js juntos).\n'
634
639
  + '- Leia um arquivo inteiro uma vez, não em pedaços pequenos; não leia de novo o que não mudou.\n'
635
- + '- Não narre o próximo passo: faça. Termine com finish("o que foi feito").';
640
+ + '- Não narre o próximo passo: faça. Termine com finish("o que foi feito").\n'
641
+ + '- VOCÊ DECIDE O CAMINHO. Pedido simples: faça direto. Pedido grande com partes independentes: write_todos com as partes, '
642
+ + 'depois spawn_agent — um por parte, até 8 NA MESMA resposta (rodam juntos), cada um com o especialista e tudo que ele precisa saber. '
643
+ + 'Cada um volta com uma ENTREGA: confira; o que falhou volta para conserto; a parte que depende de outra recebe a entrega dela no prompt.\n'
644
+ + '- Antes de dizer pronto, PROVE: rode o teste ou o comando, abra o resultado, olhe a imagem. Sem prova, não está pronto.';
645
+
646
+ /* O ritmo do subagente: o mesmo, sem a parte de orquestrar (ele não abre outro agente). */
647
+ const RITMO_AGENTE = '[ritmo]\n'
648
+ + '- O resultado de write_file/edit_file é a verdade. Não releia o que acabou de escrever.\n'
649
+ + '- Leia um arquivo INTEIRO uma vez (read_file sem offset). Não leia aos pedaços, não leia de novo o que não mudou.\n'
650
+ + '- Várias ações independentes na MESMA resposta.\n'
651
+ + '- Faça o que a sua parte pede, prove (teste ou comando) e chame finish. Polimento além do pedido não é sua parte.';
636
652
 
637
653
  /* `pedido` é opcional: com ele, o arquivo citado no pedido é o primeiro a
638
654
  entrar já lido (ver lib/pre-leitura.js). */
@@ -653,6 +669,11 @@ function buildMemoryContext(projectDir, projectName, pedido = '') {
653
669
  const lidos = preLeitura.bloco(preLeitura.escolher(projectDir, arquivos, pedido));
654
670
  if (lidos) parts.push(lidos);
655
671
  parts.push(RITMO);
672
+ // Os servidores MCP configurados, numa linha (lib/mcp.js).
673
+ try { const m = require('./mcp.js').dica(projectDir); if (m) parts.push(m); } catch { /* sem mcp */ }
674
+ // A equipe calada (lib/personas.js): o olhar do especialista que o pedido pede.
675
+ const equipe = require('./personas.js').bloco(pedido);
676
+ if (equipe) parts.push(equipe);
656
677
  return parts.join('\n\n');
657
678
  }
658
679
 
@@ -778,12 +799,9 @@ async function runAct(userPrompt, opts) {
778
799
  * não é uma opção num plano limitado por token. */
779
800
  let pedido = userPrompt;
780
801
  if (!subagent) {
781
- const comSkills = skills.montar(userPrompt);
802
+ const comSkills = skills.cardapio(userPrompt);
782
803
  pedido = comSkills.texto;
783
804
  if (instrucao) pedido = `${instrucao}\n\nPEDIDO: ${pedido}`;
784
- if (comSkills.skills.length) {
785
- fala.pensamento(`seguindo: ${comSkills.skills.map((s) => s.titulo).join(', ')}`, profundidade);
786
- }
787
805
  }
788
806
  let voltasEmFalso = 0;
789
807
  // Pesquisas que voltaram de mãos vazias. A partir da terceira, a tool
@@ -807,6 +825,9 @@ async function runAct(userPrompt, opts) {
807
825
  const memory = buildMemoryContext(projectDir, projectName, userPrompt);
808
826
  if (memory) messages.push({ role: 'system', content: `Memória persistente:\n\n${memory}` });
809
827
  for (const m of historicoEnxuto(history)) messages.push(m);
828
+ } else {
829
+ // O subagente também trabalha no ritmo: sem isto ele relia o mesmo arquivo aos pedaços.
830
+ messages.push({ role: 'system', content: RITMO_AGENTE });
810
831
  }
811
832
  /* O que VAI para o modelo é `pedido` (com as skills na frente); o que
812
833
  fica guardado na memória e no histórico é `userPrompt`, o que a pessoa
@@ -832,8 +853,14 @@ async function runAct(userPrompt, opts) {
832
853
  if (diretor.precisaDeEdicao(pedido)) {
833
854
  // A gravação da pessoa: o editor por linha do tempo (lib/video/), pelo run_shell.
834
855
  messages.push({ role: 'system', content: 'PARA EDITAR A GRAVAÇÃO DA PESSOA use o editor por linha do tempo via run_shell: '
856
+ + 'SE A PESSOA SÓ DISSE "edite esse vídeo" (sem dizer o quê), O VÍDEO É O PEDIDO: `primocode video import --json \'{"path":"<arquivo>"}\'`, '
857
+ + 'depois `primocode video auto --json \'{}\'` (lê o que ela FALA na gravação e aplica cada efeito no momento em que diz), inspect, export e abra o mp4. '
858
+ + 'Senão: '
835
859
  + 'rode `primocode video help` UMA vez (lista as operações), depois import → remove_silence/remove_words → add_captions → inspect (olhe o png) → export. '
836
- + 'Cada edição devolve um delta curto e tem undo: edite sem pedir licença a cada passo.' });
860
+ + 'Cada edição devolve um delta curto e tem undo: edite sem pedir licença a cada passo.\n'
861
+ + 'EFEITOS que seguem a pessoa: separate_layers (recorte + momento 3D), replace_background (fundo novo no estalo), '
862
+ + 'attach_3d (logo 3D na mão, segue a mão), merge_on_join (mãos juntas → fusão → cérebro), cut_silence_at (silêncio depois do estalo), '
863
+ + 'style_captions (cor, legenda em cima da cabeça) — detalhe em `primocode video help efeitos`. Atalho: `primocode video do --json \'{"pedido":"<o pedido em português>"}\'`.' });
837
864
  } else if (producao.producao) {
838
865
  /* "SE ESTIVER NA SUA LISTA" não é hesitação — é a diferença entre
839
866
  a tarefa seguir e a tarefa morrer.
@@ -854,8 +881,14 @@ async function runAct(userPrompt, opts) {
854
881
  com as primitivas" — o modelo escrevia posição em pixel e o
855
882
  vídeo saía quebrado, além de custar três ou quatro rodadas a
856
883
  mais de modelo (medido em 25/09). */
884
+ /* A IA DECIDE (01/10): "pedi um roteiro de vídeo e ela já ia fazer o
885
+ motion". A regra só sabe que a palavra "vídeo" apareceu; quem sabe
886
+ se a pessoa quer o vídeo PRONTO ou só o roteiro, a ideia ou uma
887
+ resposta é o modelo. Então isto é o caminho SE for produzir, e não
888
+ mais uma ordem de produzir. */
857
889
  messages.push({ role: 'system', content:
858
- `ESTE PEDIDO É DE PRODUÇÃO (formato "${producao.formato === 'apresentacao' ? 'slides' : 'video'}"). Faça em três passos: `
890
+ 'VOCÊ DECIDE o que entregar, pelo que a pessoa pediu: se ela quer só o roteiro, a ideia, um texto ou uma resposta, ESCREVA isso e pare — não produza vídeo. '
891
+ + `SE ela quer a peça pronta (formato "${producao.formato === 'apresentacao' ? 'slides' : 'video'}"), faça em três passos: `
859
892
  + `estudio_projeto acao:"criar" com formato "${producao.formato === 'apresentacao' ? 'slides' : 'video'}" → `
860
893
  + 'estudio_plano com "roteiro" (partes com tipo e fala; o PrimoCode faz layout, voz, ícones, música e sons) → '
861
894
  + 'estudio_render. Se a peça for de uma marca de verdade, estudio_site antes, e use as cores e a logo em roteiro.marca. '
@@ -906,10 +939,22 @@ async function runAct(userPrompt, opts) {
906
939
  const MAX_ESPERAS_REDE = Number(process.env.PRIMOCODE_MAX_ESPERAS_REDE || 6);
907
940
 
908
941
  let voltas = 0;
942
+ const teto = subagent ? MAX_VOLTAS_AGENTE : MAX_ITERATIONS;
943
+ let avisouTeto = false;
909
944
  if (!subagent) TRABALHANDO = true;
910
945
  try {
911
- for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
946
+ /* O AGENTE TEM TETO PRÓPRIO. No teste da loja (01/10) o "fechar
947
+ pendências do painel" passou de 38 voltas lendo o mesmo app.js aos
948
+ pedaços — a conversa dele inchou para 45 mil caracteres e uma resposta
949
+ levou 22 minutos. Uma parte de um plano cabe em 20 voltas; na 15ª ele
950
+ é avisado para fechar, e no teto volta com a entrega do que fez. */
951
+ for (let iteration = 0; iteration < teto && !PARAR; iteration++) {
912
952
  voltas++;
953
+ if (subagent && !avisouTeto && voltas >= teto - 5) {
954
+ avisouTeto = true;
955
+ messages.push({ role: 'user', content: `Faltam ${teto - voltas + 1} voltas para o seu limite. Feche o essencial do que foi pedido agora, `
956
+ + 'prove (rode o teste ou o comando) e chame finish dizendo o que ficou de fora. Nada de polimento extra.' });
957
+ }
913
958
  // Recado digitado durante o passo anterior entra AGORA, antes da ida
914
959
  // ao modelo — o resultado das ferramentas já está na conversa.
915
960
  if (!subagent) entregarRecados(messages, profundidade);
@@ -1178,13 +1223,13 @@ async function runAct(userPrompt, opts) {
1178
1223
  // fila — cada subagente só começava quando o anterior terminava, e a
1179
1224
  // rodada custava a soma dos três. Agora as chamadas spawn_agent da
1180
1225
  // rodada disparam de uma vez (Promise.all) e cada resultado volta na
1181
- // posição certa da conversa. Limite de 4 por rodada: acima disso é
1182
- // sinal de decomposição ruim, e o excedente roda em série mesmo.
1226
+ // posição certa da conversa. Limite de 8 por rodada (era 4: o laço do
1227
+ // Qwen despacha várias direções de uma vez); o excedente roda em série.
1183
1228
  const spawns = toolCalls.filter((tc) => tc.name === 'spawn_agent');
1184
1229
  const resultadosSpawn = new Map();
1185
1230
  if (spawns.length && !PARAR) garantirPasta();
1186
1231
  if (spawns.length > 1 && !PARAR) {
1187
- const juntos = spawns.slice(0, 4);
1232
+ const juntos = spawns.slice(0, 8);
1188
1233
  fala.paralelo(juntos.length, profundidade);
1189
1234
  const rs = await Promise.all(juntos.map((tc) =>
1190
1235
  runSubagent(tc.arguments || {}, {
@@ -1193,6 +1238,22 @@ async function runAct(userPrompt, opts) {
1193
1238
  juntos.forEach((tc, i) => resultadosSpawn.set(tc.id, rs[i]));
1194
1239
  }
1195
1240
 
1241
+ /* LEITURAS JUNTAS. O RITMO pede ao modelo para juntar as ações
1242
+ independentes numa resposta só — e ele junta: três read_file, um
1243
+ grep e uma pesquisa. Só que o laço abaixo rodava uma de cada vez, e
1244
+ a rodada custava a soma. O que só LÊ (não muda arquivo, tela nem
1245
+ conta) roda tudo de uma vez aqui; o laço usa o resultado pronto,
1246
+ passando pelos mesmos freios de sempre (repetição, registro). */
1247
+ const adiantadas = new Map();
1248
+ const leituras = toolCalls.filter(ehLeitura);
1249
+ if (leituras.length > 1 && !PARAR) {
1250
+ const rs = await Promise.all(leituras.slice(0, 8).map((tc) =>
1251
+ tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api })
1252
+ .catch((e) => ({ ok: false, error: e && e.message }))));
1253
+ leituras.slice(0, 8).forEach((tc, i) => adiantadas.set(tc.id, rs[i]));
1254
+ traco(`${adiantadas.size} leituras em paralelo`);
1255
+ }
1256
+
1196
1257
  for (const tc of toolCalls) {
1197
1258
  if (PARAR) break;
1198
1259
  if (tc.name === 'finish') {
@@ -1234,7 +1295,7 @@ async function runAct(userPrompt, opts) {
1234
1295
  server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
1235
1296
  });
1236
1297
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1237
- messages.push({ role: 'tool', tool_call_id: tc.id, content: r });
1298
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: r + lembreteDeParalelo(todosDaTarefa, spawns.length) });
1238
1299
  continue;
1239
1300
  }
1240
1301
 
@@ -1383,7 +1444,10 @@ async function runAct(userPrompt, opts) {
1383
1444
  /* O servidor, o token e o cliente da API vão junto: a legenda do
1384
1445
  estudio_video transcreve no servidor, e sem eles a ferramenta
1385
1446
  respondia "a transcrição precisa do servidor". */
1386
- const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
1447
+ traco(`executando ${tc.name}`);
1448
+ const result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
1449
+ : await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
1450
+ traco(`${tc.name} devolveu ok=${result && result.ok}`);
1387
1451
  logResult(tc.name, result, profundidade);
1388
1452
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1389
1453
  if (RELEITURA.has(tc.name) && result && result.ok !== false) {
@@ -1511,7 +1575,7 @@ async function runAct(userPrompt, opts) {
1511
1575
  // não dá nem para saber que acabou. Medido: um pedido de navegador morria assim
1512
1576
  // depois de a primeira tentativa de seletor falhar.
1513
1577
  if (!finalText) {
1514
- finalText = motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou: rodadasParadas >= MAX_RODADAS_PARADAS });
1578
+ finalText = motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou: rodadasParadas >= MAX_RODADAS_PARADAS, teto });
1515
1579
  if (!subagent) fala.aviso(finalText);
1516
1580
  }
1517
1581
 
@@ -1589,7 +1653,42 @@ async function runSubagent(args, ctx) {
1589
1653
 
1590
1654
  const resumo = r.assistantMessage.content || 'sem resultado';
1591
1655
  fala.agenteVoltou(resumo, profundidade);
1592
- return resumo;
1656
+ return entregaDoAgente(especialistas.rotulo(esp), resumo, r.registro);
1657
+ }
1658
+
1659
+ /* UM AGENTE DE CADA VEZ é o padrão do modelo grátis: no teste da loja
1660
+ (API + site + painel, 01/10) ele despachou os três em fila — 6 + 8 + 5
1661
+ minutos — quando o site e o painel só dependiam do contrato, que já
1662
+ existia. Quem decide continua sendo ele; a volta só lembra que as partes
1663
+ que faltam podem ir JUNTAS. Pura. */
1664
+ function lembreteDeParalelo(todos, naRodada) {
1665
+ const faltam = (todos || []).filter((t) => t && t.status !== 'completed' && t.status !== 'concluida');
1666
+ if (naRodada > 1 || faltam.length < 2) return '';
1667
+ return `\n\n(Faltam ${faltam.length} partes do plano. As que não dependem uma da outra vão JUNTAS: `
1668
+ + 'vários spawn_agent na MESMA resposta rodam em paralelo.)';
1669
+ }
1670
+
1671
+ /* O SALTO ENTRE AGENTES. O pai recebia só o texto final do especialista —
1672
+ "pronto, fiz a API" — e, para passar a vez ao próximo (o do front, o
1673
+ revisor), tinha de adivinhar o que existia. Agora a volta é uma ENTREGA:
1674
+ quem fez, o que disse, os arquivos que nasceram e mudaram, os comandos, o
1675
+ que falhou. É o contexto que o próximo especialista precisa no prompt
1676
+ dele, e é o que deixa o pai mandar a falha de volta para conserto (o laço
1677
+ do Qwen: planeja → despacha → confere → conserta). Pura, para o teste. */
1678
+ function entregaDoAgente(quem, texto, reg) {
1679
+ const linhas = [`ENTREGA DO ${String(quem).toUpperCase()}`, String(texto).trim().slice(0, 4000)];
1680
+ if (reg) {
1681
+ const lista = (nome, xs) => { if (xs && xs.length) linhas.push(`${nome}: ${xs.slice(0, 15).join(', ')}`); };
1682
+ lista('criou', reg.criados);
1683
+ lista('alterou', reg.alterados);
1684
+ lista('rodou', (reg.comandos || []).map((x) => `${x.comando}${x.ok === false ? ` (saiu ${x.code})` : ''}`));
1685
+ lista('testes', (reg.testes || []).map((x) => `${x.comando}: ${x.ok ? 'passou' : 'FALHOU'}`));
1686
+ if (reg.falhas && reg.falhas.length) linhas.push('FALHOU: ' + reg.falhas.slice(0, 6).map((f) => `${f.acao} ${f.alvo}: ${f.motivo}`.slice(0, 200)).join(' | '));
1687
+ }
1688
+ linhas.push(reg && ((reg.falhas && reg.falhas.length) || (reg.testes || []).some((x) => !x.ok))
1689
+ ? 'PRÓXIMO: há falha acima — mande de volta para conserto (spawn_agent com o erro no prompt) ou conserte você, antes de seguir.'
1690
+ : 'PRÓXIMO: se outra parte depende desta, passe a vez (spawn_agent) com esta entrega no prompt; senão, confira e siga.');
1691
+ return linhas.join('\n');
1593
1692
  }
1594
1693
 
1595
1694
  /* ══ O PIPELINE DE QUATRO PAPÉIS ═══════════════════════════════════════════
@@ -1702,5 +1801,5 @@ async function rodarPipeline(args, ctx) {
1702
1801
  });
1703
1802
  }
1704
1803
 
1705
- module.exports = { runAct, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1804
+ module.exports = { runAct, entregaDoAgente, lembreteDeParalelo, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1706
1805
  anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
package/lib/browser.js CHANGED
@@ -23,9 +23,36 @@ const http = require('http');
23
23
  const crypto = require('crypto');
24
24
  const { spawn, execSync } = require('child_process');
25
25
 
26
- const PROFILE_DIR = path.join(os.homedir(), '.primocode', 'chrome-profile');
26
+ const PERFIL_VISIVEL = path.join(os.homedir(), '.primocode', 'chrome-profile');
27
27
  // Dentro da faixa 3100-3199 do ambiente local, para o start.sh cuidar dela.
28
- const DEBUG_PORT = Number(process.env.PRIMOCODE_CDP_PORT || 3199);
28
+ const PORTA_VISIVEL = Number(process.env.PRIMOCODE_CDP_PORT || 3199);
29
+
30
+ /* ── VISÍVEL SÓ QUANDO A PESSOA PEDIU ────────────────────────────────────
31
+ * "toda vez que eu mando alguma coisa pra ele ele abre umas 10.000 abas
32
+ * do Chrome — ele nem usa — e eu fecho e ele abre de novo" (Joel, 02/10)
33
+ * As ferramentas browser_* vão para o modelo em todo pedido, e ele as usa
34
+ * para PESQUISAR (o motion da Paris Group subiu o Chrome às 09:41 sem
35
+ * ninguém pedir navegador). Agora o terminal diz, por pedido, se a pessoa
36
+ * quer ver (definirVisivel): sem pedido de navegador, as mesmas ferramentas
37
+ * rodam num Chrome --headless de perfil e porta próprios, que nunca abre
38
+ * janela nem aba na tela — e morre com o processo. */
39
+ const PERFIL_ESCONDIDO = path.join(os.homedir(), '.primocode', 'chrome-escondido');
40
+ const PORTA_ESCONDIDA = Number(process.env.PRIMOCODE_CDP_PORT_ESCONDIDO || 3197);
41
+ let visivel = true;
42
+ let DEBUG_PORT = PORTA_VISIVEL;
43
+ let PROFILE_DIR = PERFIL_VISIVEL;
44
+
45
+ function definirVisivel(sim) {
46
+ const quer = sim !== false;
47
+ if (quer === visivel) return visivel;
48
+ visivel = quer;
49
+ DEBUG_PORT = quer ? PORTA_VISIVEL : PORTA_ESCONDIDA;
50
+ PROFILE_DIR = quer ? PERFIL_VISIVEL : PERFIL_ESCONDIDO;
51
+ // A sessão aberta é do outro navegador: a próxima ação conecta no certo.
52
+ if (currentSession) { try { currentSession.close(); } catch { /* já fechou */ } currentSession = null; }
53
+ return visivel;
54
+ }
55
+ function estaVisivel() { return visivel; }
29
56
 
30
57
  // ---- Helpers ---------------------------------------------------
31
58
  /* O Chromium que o Playwright baixa. Ele não fica no PATH nem em /Applications
@@ -414,12 +441,20 @@ async function ensureChromeRunning() {
414
441
  args.push('--no-sandbox');
415
442
  }
416
443
  // Sem interface gráfica (CI, servidor): PRIMOCODE_BROWSER_HEADLESS=1.
417
- if (process.env.PRIMOCODE_BROWSER_HEADLESS === '1' || process.env.PRIMOCODE_BROWSER_HEADLESS === 'true') {
418
- args.push('--headless=new', '--disable-gpu', '--disable-dev-shm-usage');
444
+ // E sempre que a pessoa não pediu para ver (definirVisivel).
445
+ const escondido = !visivel;
446
+ if (escondido || process.env.PRIMOCODE_BROWSER_HEADLESS === '1' || process.env.PRIMOCODE_BROWSER_HEADLESS === 'true') {
447
+ args.push('--headless=new', '--disable-gpu', '--disable-dev-shm-usage', '--mute-audio');
419
448
  }
420
449
  args.push('about:blank');
421
450
  chromeChild = spawn(chrome, args, { detached: true, stdio: 'ignore' });
422
451
  chromeChild.on('error', () => {});
452
+ /* O escondido morre com o processo: ninguém o vê para fechar, e um
453
+ headless órfão por sessão era o que o revisor deixava em 24/09. */
454
+ if (escondido) {
455
+ const filho = chromeChild;
456
+ process.once('exit', () => { try { process.kill(-filho.pid); } catch { try { filho.kill(); } catch { /* já morreu */ } } });
457
+ }
423
458
  chromeChild.unref();
424
459
 
425
460
  // Espera a porta responder
@@ -492,6 +527,55 @@ async function open(url) {
492
527
  return { ok: true, url };
493
528
  }
494
529
 
530
+ /* ── ABRIR À VISTA ────────────────────────────────────────────────────────
531
+ * "Peço pra ele abrir o Chrome e o YouTube: em vez de só abrir, ele mostra
532
+ * o mouse dele abrindo o Chrome, pesquisando…" (30/09)
533
+ * O cursor do PrimoCode (voz/primo_voz/cursor.py, macOS) vai até a barra de
534
+ * endereço da janela de verdade, clica, digita o endereço letra por letra e
535
+ * aperta Enter — quem olha vê o computador sendo usado. A janela vem do CDP
536
+ * (Browser.getWindowForTarget), então o clique cai na barra mesmo com a
537
+ * janela em qualquer lugar da tela. Sem cursor (outro SO, sem permissão),
538
+ * navega direto, como antes. */
539
+ async function abrirVisivel(url) {
540
+ // Escondido não tem janela para o cursor ir: navega direto.
541
+ if (!visivel) return open(url);
542
+ await ensure();
543
+ const cursor = require('./cursor.js');
544
+ if (!verSeta() || !cursor.disponivel()) return open(url);
545
+ try { await currentSession.send('Page.bringToFront'); } catch { /* segue */ }
546
+ let janela = null;
547
+ try {
548
+ const alvo = await currentSession.send('Target.getTargetInfo');
549
+ const w = await currentSession.send('Browser.getWindowForTarget', alvo && alvo.targetInfo ? { targetId: alvo.targetInfo.targetId } : {});
550
+ janela = w && w.bounds;
551
+ } catch { janela = null; }
552
+ if (!janela || janela.left == null) return open(url);
553
+ // A barra fica uns 58 pt abaixo do topo da janela (abas + barra), a ~30% da largura.
554
+ const x = Math.round(janela.left + Math.min(420, janela.width * 0.3));
555
+ const y = Math.round(janela.top + 58);
556
+ const passos = [
557
+ ['clicar', { x, y, botao: 'esquerdo', rotulo: 'barra de endereço' }],
558
+ ['tecla', { combo: 'cmd+a' }],
559
+ ['digitar', { texto: url.replace(/^https?:\/\//, ''), rotulo: 'digitando o endereço' }],
560
+ ['tecla', { combo: 'enter' }],
561
+ ];
562
+ for (const [cmd, args] of passos) {
563
+ const r = await cursor.mandar(cmd, args);
564
+ if (!r || r.ok === false) return open(url); // sem permissão de Acessibilidade: navega direto
565
+ }
566
+ const deadline = Date.now() + 20000;
567
+ await sleep(600);
568
+ while (Date.now() < deadline) {
569
+ try {
570
+ const r = await currentSession.send('Runtime.evaluate', { expression: 'document.readyState + "|" + location.href', returnByValue: true });
571
+ const [estado, href] = String((r.result && r.result.value) || '').split('|');
572
+ if (estado === 'complete' && href && href !== 'about:blank') return { ok: true, url: href, visivel: true };
573
+ } catch { /* a página está trocando */ }
574
+ await sleep(200);
575
+ }
576
+ return { ok: true, url, visivel: true };
577
+ }
578
+
495
579
  async function evaluate(js) {
496
580
  await ensure();
497
581
  const r = await currentSession.send('Runtime.evaluate', {
@@ -1207,7 +1291,7 @@ function fecharRevisor() {
1207
1291
  }
1208
1292
 
1209
1293
  module.exports = {
1210
- retrato,
1294
+ retrato, abrirVisivel, definirVisivel, estaVisivel,
1211
1295
  SEM_KEYCHAIN,
1212
1296
  scriptDaSeta, verSeta, findChrome,
1213
1297
  open, click, clickText, clickSelector, type, typeInSelector,
package/lib/catalogo.js CHANGED
@@ -89,7 +89,7 @@ const GRUPOS = [
89
89
  + 'Tipos: abertura{titulo,sub} titulo{titulo,sub} numero{valor,rotulo,sub} lista{titulo,itens} passos{titulo,itens} '
90
90
  + 'contraste{nao,sim} citacao{texto,autor} imagem{busca,titulo,sub} grafico{titulo,barras:[{rotulo,valor}]} cta{titulo,link} '
91
91
  + 'logo{titulo} cores{cores:[{nome,hex}]} produto{titulo,texto,destaque,selo,barras}. Em qualquer parte: destaque (a palavra-tese), fonte (a fonte citada). '
92
- + 'estilo: motion (padrão, 3D contínuo) | vox (explainer editorial: tinta sobre papel, câmera que conta a frase) | '
92
+ + 'estilo: hibrido (PADRÃO: foto e vídeo reais que não param + o motion por cima; dê busca:"termo em inglês" por parte para o b-roll) | gerativo (= motion: sem template — a tipografia anima nos eixos da fonte numa linha do tempo contínua, composição, fonte, cor e som sorteados do roteiro; é o do modo sem referências e o de quem pede motion/animação; com modelo3d o produto 3D entra numa batida) | classico (o motion 3D antigo) | vox (explainer editorial: tinta sobre papel, câmera que conta a frase) | '
93
93
  + 'marca (brand reveal: brilho, letras em vetor, marca cromada, padrão) | lancamento (produto: cartões, painel, calendário) | impacto | cinema. '
94
94
  + 'marca e lancamento aceitam visual:orbe|vetor|estrela|esfera|grade|site|padrao|cromo|cartoes|painel|cores numa parte. marca.nome dá o nome. '
95
95
  + 'voz (thalita|antonio|francisca). Ajuste fino: plano: {formato:{largura,altura,fps}, cenas:[…], audio:{…}} — ou cenas direto. '
@@ -575,6 +575,40 @@ function promptDoProtocolo(payload) {
575
575
  + '\n\nResponda com exatamente UM bloco marcado.';
576
576
  }
577
577
 
578
+ /* O `claude -p` ASSÍNCRONO. Era execFileSync: enquanto o Claude pensava (um
579
+ minuto, dois), o PrimoCode inteiro parava — sem spinner, sem recado, sem
580
+ Esc Esc. E o teto de 180 s estourava justo quando ele escrevia um arquivo
581
+ grande, e o erro saía cru na tela ("Command failed: claude -p …"). Agora
582
+ roda em paralelo, com 10 min de teto, uma segunda tentativa, e o erro
583
+ diz o que o Claude disse. */
584
+ function rodarClaude(args, input, { timeout = 600000, tentativas = 2 } = {}) {
585
+ const { spawn } = require('child_process');
586
+ const umaVez = () => new Promise((resolve, reject) => {
587
+ const p = spawn('claude', args, { env: process.env, stdio: ['pipe', 'pipe', 'pipe'], ...(SEM_APARECER.windowsHide ? { windowsHide: true } : {}) });
588
+ let out = '', err = '';
589
+ const relogio = setTimeout(() => { try { p.kill(); } catch {} reject(new Error(`o Claude Code não respondeu em ${Math.round(timeout / 60000)} min`)); }, timeout);
590
+ p.stdout.on('data', (d) => { out += d; });
591
+ p.stderr.on('data', (d) => { err += d; });
592
+ p.on('error', (e) => { clearTimeout(relogio); reject(e); });
593
+ p.on('close', (code) => {
594
+ clearTimeout(relogio);
595
+ if (code === 0) return resolve(out);
596
+ // O JSON às vezes vem com is_error e o motivo, mesmo com código != 0.
597
+ let motivo = '';
598
+ try { const j = JSON.parse(out); motivo = j.result || j.error || ''; } catch { /* sem json */ }
599
+ reject(new Error(`o Claude Code saiu com ${code}: ${String(motivo || err || out).replace(/\s+/g, ' ').trim().slice(0, 300)}`));
600
+ });
601
+ p.stdin.end(input);
602
+ });
603
+ return (async () => {
604
+ let ultimo;
605
+ for (let i = 0; i < tentativas; i++) {
606
+ try { return await umaVez(); } catch (e) { ultimo = e; }
607
+ }
608
+ throw ultimo;
609
+ })();
610
+ }
611
+
578
612
  async function requestActViaHeadless(payload, onEvent) {
579
613
  const corpoBootstrap = corpoDoProtocolo();
580
614
  const prompt = promptDoProtocolo(payload);
@@ -585,27 +619,14 @@ async function requestActViaHeadless(payload, onEvent) {
585
619
  // O prompt vai por STDIN, nunca por argumento: argumento tem limite de
586
620
  // tamanho (a conversa cresce) e qualquer conteúdo começando com traço
587
621
  // seria lido como opção. stdin não tem nenhum dos dois problemas.
588
- const saida = execFileSync('claude', [
589
- '-p',
590
- '--output-format', 'json',
591
- '--dangerously-skip-permissions',
592
- ...argsDoMotor(),
593
- ], { encoding: 'utf-8', timeout: 180000, input: prompt, maxBuffer: 32 * 1024 * 1024,
594
- env: process.env, ...SEM_APARECER });
622
+ const saida = await rodarClaude(['-p', '--output-format', 'json', '--dangerously-skip-permissions', ...argsDoMotor()], prompt);
595
623
  const r = JSON.parse(saida);
596
624
  // A correção é uma segunda ida ao motor, com o mesmo protocolo e o
597
625
  // diagnóstico do que saiu errado. Barata: o prompt é curto, e evita que
598
626
  // um erro de sintaxe do motor vire erro na cara do usuário.
599
- const pedirCorrecao = (aviso) => {
600
- const s = execFileSync('claude', [
601
- '-p', '--output-format', 'json', '--dangerously-skip-permissions',
602
- ...argsDoMotor(),
603
- ], {
604
- encoding: 'utf-8', timeout: 120000, maxBuffer: 32 * 1024 * 1024,
605
- env: process.env,
606
- input: promptDeCorrecao(aviso),
607
- ...SEM_APARECER,
608
- });
627
+ const pedirCorrecao = async (aviso) => {
628
+ const s = await rodarClaude(['-p', '--output-format', 'json', '--dangerously-skip-permissions', ...argsDoMotor()],
629
+ promptDeCorrecao(aviso), { timeout: 300000, tentativas: 1 });
609
630
  return JSON.parse(s).result || '';
610
631
  };
611
632
  await emitirDecisao(r.result || '', onEvent, pedirCorrecao, 0, nomesDasTools(payload));