primocode 9.8.0-beta.2 → 9.8.0-beta.21

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 (74) hide show
  1. package/README.md +33 -6
  2. package/app/ansi.js +55 -0
  3. package/app/app.css +482 -281
  4. package/app/app.js +888 -315
  5. package/app/index.html +176 -101
  6. package/app/primo-marca.js +58 -1
  7. package/bin/primocode.js +697 -118
  8. package/comandos/explicar.md +14 -0
  9. package/comandos/publicar.md +10 -0
  10. package/comandos/revisar.md +13 -0
  11. package/lib/act.js +130 -26
  12. package/lib/api.js +8 -1
  13. package/lib/app.js +317 -74
  14. package/lib/browser.js +79 -3
  15. package/lib/catalogo.js +11 -3
  16. package/lib/checkpoint-git.js +218 -0
  17. package/lib/checkpoint.js +249 -0
  18. package/lib/claude-engine.js +25 -3
  19. package/lib/comandos-extra.js +481 -0
  20. package/lib/comandos.js +104 -0
  21. package/lib/config.js +21 -4
  22. package/lib/estudio/acervo-site.js +157 -0
  23. package/lib/estudio/assets.js +77 -3
  24. package/lib/estudio/cinema.js +354 -0
  25. package/lib/estudio/composicao.js +470 -0
  26. package/lib/estudio/edicao.js +450 -0
  27. package/lib/estudio/impacto.js +263 -0
  28. package/lib/estudio/index.js +769 -5
  29. package/lib/estudio/plano.js +5 -2
  30. package/lib/estudio/remotion.js +228 -23
  31. package/lib/estudio/slides.js +34 -30
  32. package/lib/fala.js +95 -1
  33. package/lib/local/cerebro.json +361 -180
  34. package/lib/local/entrada.js +18 -8
  35. package/lib/local/motor-cli.js +1 -1
  36. package/lib/mencoes.js +98 -0
  37. package/lib/offline.js +168 -0
  38. package/lib/painel.js +291 -0
  39. package/lib/perigo.js +256 -0
  40. package/lib/pre-leitura.js +118 -0
  41. package/lib/repeticao.js +4 -2
  42. package/lib/skills.js +6 -1
  43. package/lib/tempos.js +62 -0
  44. package/lib/ui.js +171 -17
  45. package/package.json +3 -2
  46. package/skills/apresentacao-pptx/SKILL.md +52 -0
  47. package/skills/cartaz-de-arte/LICENSE.txt +202 -0
  48. package/skills/cartaz-de-arte/SKILL.md +47 -0
  49. package/skills/criar-skill/LICENSE.txt +202 -0
  50. package/skills/criar-skill/SKILL.md +49 -0
  51. package/skills/depurar-de-verdade/LICENSE.txt +21 -0
  52. package/skills/depurar-de-verdade/SKILL.md +14 -5
  53. package/skills/design-de-verdade/LICENSE.txt +177 -0
  54. package/skills/design-de-verdade/SKILL.md +42 -0
  55. package/skills/documento-word/SKILL.md +57 -0
  56. package/skills/interface-revisada/SKILL.md +47 -0
  57. package/skills/me-ensina/LICENSE.txt +21 -0
  58. package/skills/me-ensina/SKILL.md +27 -0
  59. package/skills/me-entrevista/LICENSE.txt +21 -0
  60. package/skills/me-entrevista/SKILL.md +27 -0
  61. package/skills/motion-de-primeira/SKILL.md +1 -1
  62. package/skills/pdf-bonito/SKILL.md +48 -0
  63. package/skills/planilha-excel/SKILL.md +47 -0
  64. package/skills/react-sem-dor/SKILL.md +48 -0
  65. package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
  66. package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
  67. package/skills/site-com-motion/SKILL.md +39 -0
  68. package/skills/temas-prontos/LICENSE.txt +202 -0
  69. package/skills/temas-prontos/SKILL.md +50 -0
  70. package/skills/testar-o-site/LICENSE.txt +202 -0
  71. package/skills/testar-o-site/SKILL.md +46 -0
  72. package/skills/video-no-remotion/SKILL.md +48 -0
  73. package/studio/narrar.py +111 -10
  74. package/studio/rostos.py +52 -0
@@ -0,0 +1,14 @@
1
+ ---
2
+ descricao: explica este projeto (ou um arquivo) para quem não programa
3
+ ---
4
+ Me explique como se eu nunca tivesse programado: $ARGUMENTOS
5
+
6
+ Se eu não disse o que explicar, explique o projeto inteiro desta pasta. Leia os arquivos antes de falar, não chute.
7
+
8
+ Responda nesta ordem, em frases curtas:
9
+ 1. o que isto faz, em uma frase;
10
+ 2. as partes principais e o papel de cada uma (use comparações do dia a dia);
11
+ 3. como eu rodo e vejo funcionando;
12
+ 4. onde eu mexo para mudar o que é mais provável que eu queira mudar.
13
+
14
+ Sem jargão. Se uma palavra técnica for inevitável, explique ela na hora.
@@ -0,0 +1,10 @@
1
+ ---
2
+ descricao: guia passo a passo para colocar o projeto no ar, com link público
3
+ ---
4
+ Quero colocar este projeto no ar, com um link que eu possa mandar para as pessoas. $ARGUMENTOS
5
+
6
+ Primeiro olhe o projeto e me diga, em uma frase, que tipo de coisa ele é (site estático, app com servidor, etc.). Depois recomende UM caminho gratuito que sirva para ele (por exemplo GitHub Pages, Vercel, Netlify ou Railway) e diga por que esse.
7
+
8
+ Me guie um passo por vez, esperando eu confirmar antes do próximo. Faça você o que der para fazer pelo terminal; o que só eu posso fazer (criar conta, clicar num botão no site) me diga exatamente onde clicar.
9
+
10
+ Nunca coloque senha, chave ou arquivo .env no que for publicado. Se achar um, pare e me avise.
@@ -0,0 +1,13 @@
1
+ ---
2
+ descricao: revisa o projeto (ou o que mudou) atrás de bugs, e explica sem jargão
3
+ ---
4
+ Revise este projeto procurando bugs e coisas que vão quebrar. Se for um repositório git com mudanças ainda não salvas, comece pelo que mudou (git status e git diff); se não, olhe os arquivos principais.
5
+
6
+ Foco, se eu disser: $ARGUMENTOS
7
+
8
+ Para cada problema que achar, diga em linguagem simples, como para quem não programa:
9
+ 1. o que está errado e onde (arquivo e linha);
10
+ 2. o que acontece com quem usa, se ninguém consertar;
11
+ 3. como consertar.
12
+
13
+ Ordene do mais grave para o menos grave. Não conserte nada ainda: só mostre a lista e pergunte quais eu quero que você arrume.
package/lib/act.js CHANGED
@@ -21,7 +21,11 @@ const repeticao = require('./repeticao');
21
21
  const diff = require('./diff');
22
22
  const registro = require('./registro');
23
23
  const permissao = require('./permissao');
24
+ const perigo = require('./perigo');
24
25
  const skills = require('./skills');
26
+ const checkpoint = require('./checkpoint');
27
+ const tempos = require('./tempos');
28
+ const preLeitura = require('./pre-leitura');
25
29
 
26
30
  // 40, não 15: com o freio de repetição (repeticao.js) e o de rodada parada,
27
31
  // volta a mais não é volta em círculo — é trabalho. Quinze acabavam no meio
@@ -33,6 +37,8 @@ const MAX_EMPURROES = 3;
33
37
  // Voltas seguidas sem executar nada antes de desistir.
34
38
  const MAX_RODADAS_PARADAS = 3;
35
39
  const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
40
+ // As leituras que, repetidas sem mudança no meio, devolvem o guardado.
41
+ const RELEITURA = new Set(['read_file', 'list_dir']);
36
42
 
37
43
  // ── Interrupção (Esc Esc) ─────────────────────────────────────────────────
38
44
  // O usuário aperta Esc duas vezes e o agente PARA — entre uma rodada e outra
@@ -132,6 +138,9 @@ function limparParaOModelo(result) {
132
138
 
133
139
  function logResult(kind, result, profundidade) {
134
140
  const p = fala.resultado(profundidade);
141
+ // A linha da janela (lib/app.js) fecha aqui: o mesmo resultado que o
142
+ // terminal desenha embaixo, resumido. Ver o canal em lib/fala.js.
143
+ fala.avisar('resultado', fala.resumoDoResultado(kind, result));
135
144
  if (!result) return;
136
145
  // Uma tool que falhou é informação para o AGENTE, que vai consertar na
137
146
  // volta seguinte — não é o erro do usuário. Âmbar, uma linha, sem o
@@ -380,6 +389,7 @@ async function pausar(ms, subagent) {
380
389
  const sp = subagent ? null : new Spinner(PENSANDO).start();
381
390
  await new Promise((r) => setTimeout(r, ms));
382
391
  if (sp) sp.stop();
392
+ tempos.somar('espera de cota/rede', ms);
383
393
  }
384
394
 
385
395
  /**
@@ -449,10 +459,10 @@ function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou }) {
449
459
  const PULAR_NO_MAPA = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'out',
450
460
  '.cache', 'coverage', '__pycache__', '.venv', 'venv', '.turbo', '.vercel', '.idea', '.vscode']);
451
461
 
452
- function mapaDoProjeto(dir, limite = 80) {
462
+ function arquivosDoProjeto(dir, limite = 80) {
453
463
  const path = require('path');
454
464
  const fs = require('fs');
455
- if (!dir || path.resolve(dir) === path.resolve(cfg.ROOT_DIR)) return '';
465
+ if (!dir || path.resolve(dir) === path.resolve(cfg.ROOT_DIR)) return [];
456
466
  const achados = [];
457
467
  const andar = (atual, fundo) => {
458
468
  if (fundo > 4 || achados.length >= limite) return;
@@ -468,19 +478,33 @@ function mapaDoProjeto(dir, limite = 80) {
468
478
  }
469
479
  };
470
480
  andar(dir, 0);
481
+ return achados;
482
+ }
483
+
484
+ function mapaDoProjeto(dir, limite = 80, achados = arquivosDoProjeto(dir, limite)) {
471
485
  if (!achados.length) return '';
472
486
  const mais = achados.length >= limite ? `\n… (mais arquivos — use glob para achar)` : '';
473
487
  return `Arquivos que JÁ existem neste projeto (edite-os em vez de recriar):\n${achados.join('\n')}${mais}`;
474
488
  }
475
489
 
476
- function buildMemoryContext(projectDir, projectName) {
490
+ /* `pedido` é opcional: com ele, o arquivo citado no pedido é o primeiro a
491
+ entrar já lido (ver lib/pre-leitura.js). */
492
+ function buildMemoryContext(projectDir, projectName, pedido = '') {
477
493
  const parts = [];
494
+ // As instruções vêm primeiro: são regra de quem manda no projeto, e a
495
+ // memória é o que o agente anotou. Entram aqui (e não no bin) para valer
496
+ // igual no terminal, no modo voz e em cada passo de um plano.
497
+ const instrucoes = require('./mencoes.js').instrucoesDoProjeto(projectDir);
498
+ if (instrucoes) parts.push(instrucoes);
478
499
  const geral = cfg.readGeneralMemory();
479
500
  if (geral.trim()) parts.push(`[memory-geral.md]\n${geral}`);
480
501
  const projeto = cfg.readProjectMemory(projectDir);
481
502
  if (projeto.trim()) parts.push(`[${projectName || 'projeto'}/memory.md]\n${projeto}`);
482
- const mapa = mapaDoProjeto(projectDir);
483
- if (mapa) parts.push(`[${projectName || 'projeto'}/arquivos]\n${mapa}`);
503
+ const arquivos = arquivosDoProjeto(projectDir);
504
+ const mapa = mapaDoProjeto(projectDir, 80, arquivos);
505
+ if (mapa) parts.push(`[${projectName || 'projeto'}/arquivos]\n${mapa}\n\n${preLeitura.JUNTAR_LEITURAS}`);
506
+ const lidos = preLeitura.bloco(preLeitura.escolher(projectDir, arquivos, pedido));
507
+ if (lidos) parts.push(lidos);
484
508
  return parts.join('\n\n');
485
509
  }
486
510
 
@@ -493,6 +517,10 @@ async function runAct(userPrompt, opts) {
493
517
  quem sabe lembrar é a SESSÃO — um subagente não pode começar do
494
518
  zero e pedir de novo o que já foi autorizado. */
495
519
  autorizar = null, controle = null,
520
+ /* O modo do /permissoes (auto · perguntar · livre). Vem da config pela
521
+ CLI e desce para os subagentes pelo ctx: eles recusam o perigoso,
522
+ mas só sabem que é "livre" se alguém disser. */
523
+ permissoes = 'auto',
496
524
  /* O cliente HTTP, injetável. NÃO é mais um comutador de motor — o
497
525
  PrimoCode tem um motor só, o do servidor, e não há provedor para o
498
526
  usuário escolher. Continua um parâmetro porque é por aqui que o
@@ -509,6 +537,11 @@ async function runAct(userPrompt, opts) {
509
537
  /* Quem abre a página para conferir antes do "pronto". Injetável pelo
510
538
  mesmo motivo do `motor`: o teste mede a regra sem abrir um Chrome. */
511
539
  medirPagina = (dir) => require('./pipeline.js').medirNoNavegador(dir),
540
+ /* Quem LIGA o navegador escondido antes da hora. Injetável pelo mesmo
541
+ motivo do `medirPagina` — e só vale junto com o padrão dele: um
542
+ teste que troca o medidor não pode subir um Chrome de verdade. */
543
+ aquecerPagina = opts && opts.medirPagina ? null
544
+ : () => require('./browser.js').aquecerRevisor(),
512
545
  } = opts || {};
513
546
 
514
547
  // A pasta de projeto só nasce quando o agente for mesmo MEXER EM ARQUIVO.
@@ -539,6 +572,7 @@ async function runAct(userPrompt, opts) {
539
572
  let mexeuNaPagina = false; // mexeu desde a última conferência?
540
573
  let conferencias = 0; // até duas: entregar, e conferir o conserto
541
574
  let avisoDaPagina = ''; // o que ainda estava quebrado na última
575
+ let msConferindo = 0; // para o PRIMOCODE_TEMPOS separar as fases
542
576
  /* Abre a página e devolve o relato quando é para VOLTAR (achou problema e
543
577
  ainda cabe uma volta); null quando é para entregar. O que ficar
544
578
  quebrado na última conferência fica em `avisoDaPagina`, e a entrega sai
@@ -549,10 +583,13 @@ async function runAct(userPrompt, opts) {
549
583
  conferencias++;
550
584
  mexeuNaPagina = false;
551
585
  let medida = null;
586
+ const t = Date.now();
552
587
  try {
553
588
  fala.pensamento('abrindo a página para conferir antes de entregar', profundidade);
554
589
  medida = await medirPagina(pastaAtual);
555
590
  } catch { medida = null; }
591
+ msConferindo += Date.now() - t;
592
+ tempos.somar('conferir página', Date.now() - t);
556
593
  if (!medida || !medida.testou) return null;
557
594
  avisoDaPagina = medida.limpo ? ''
558
595
  : String(medida.relato || '').split('\n').filter((l) => /·|BRANCO/.test(l)).slice(0, 3).join(' ');
@@ -563,6 +600,8 @@ async function runAct(userPrompt, opts) {
563
600
  ? `${texto}\n\nAtenção: abri a página e ela ainda tem problema — ${avisoDaPagina.trim()}` : texto);
564
601
  // Quem já foi chamado, e quantas vezes. Ver lib/repeticao.js.
565
602
  const jaChamado = repeticao.novo();
603
+ // O que read_file/list_dir devolveram, pela chave da época (repeticao.js).
604
+ const leiturasGuardadas = new Map();
566
605
  /* O livro deste pedido: arquivos, comandos, links, testes e falhas. Sai
567
606
  no fim como balanço (ver lib/registro.js). O subagente tem o seu, que
568
607
  não se mistura com o do principal — mas o que ele fez também precisa
@@ -612,7 +651,7 @@ async function runAct(userPrompt, opts) {
612
651
 
613
652
  const messages = [];
614
653
  if (!subagent) {
615
- const memory = buildMemoryContext(projectDir, projectName);
654
+ const memory = buildMemoryContext(projectDir, projectName, userPrompt);
616
655
  if (memory) messages.push({ role: 'system', content: `Memória persistente:\n\n${memory}` });
617
656
  for (const m of (history || []).slice(-20)) {
618
657
  if (m && m.role && m.content) messages.push({ role: m.role, content: m.content });
@@ -653,15 +692,19 @@ async function runAct(userPrompt, opts) {
653
692
 
654
693
  Um modelo não chama tool fora da lista por conta própria. Só
655
694
  chamou porque este parágrafo mandou. */
695
+ /* O caminho do ROTEIRO (lib/estudio/composicao.js). Antes esta
696
+ ordem mandava chamar o diretor e depois compor "cena a cena,
697
+ com as primitivas" — o modelo escrevia posição em pixel e o
698
+ vídeo saía quebrado, além de custar três ou quatro rodadas a
699
+ mais de modelo (medido em 25/09). */
656
700
  messages.push({ role: 'system', content:
657
- 'ESTE PEDIDO É DE PRODUÇÃO AUDIOVISUAL. Se `planejar_producao` estiver na sua lista de '
658
- + `ferramentas, chame-o ANTES de criar qualquer peça (formato "${producao.formato}"): `
659
- + 'ele devolve a pauta — arco, narração falada, voz, direção de arte e a mídia a buscar — '
660
- + 'e é ela que você produz, cena a cena, compondo com as primitivas. '
661
- + 'Se ele NÃO estiver na sua lista, não o chame e não procure equivalente: siga com as '
662
- + 'ferramentas que você tem e escreva o roteiro antes das cenas, não dentro do JSON delas. '
663
- + 'Nunca chame uma ferramenta que não está na sua lista — a chamada é recusada e a tarefa '
664
- + 'se perde.' });
701
+ `ESTE PEDIDO É DE PRODUÇÃO (formato "${producao.formato === 'apresentacao' ? 'slides' : 'video'}"). Faça em três passos: `
702
+ + `estudio_projeto acao:"criar" com formato "${producao.formato === 'apresentacao' ? 'slides' : 'video'}" → `
703
+ + 'estudio_plano com "roteiro" (partes com tipo e fala; o PrimoCode faz layout, voz, ícones, música e sons) → '
704
+ + '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. '
705
+ + 'NÃO escreva cenas, elementos nem posições à mão. '
706
+ + 'Se estudio_plano não aceitar "roteiro" (servidor antigo), siga com as ferramentas que você tem. '
707
+ + 'Nunca chame uma ferramenta que não está na sua lista — a chamada é recusada e a tarefa se perde.' });
665
708
  }
666
709
  }
667
710
 
@@ -724,6 +767,7 @@ async function runAct(userPrompt, opts) {
724
767
  let fluxo = null;
725
768
 
726
769
  emVoo = new AbortController();
770
+ const tRodada = Date.now();
727
771
  try {
728
772
  await motor.requestAct(server, { messages: compactar(messages), model, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
729
773
  if (evt.erro) { streamError = evt.erro; return; }
@@ -770,6 +814,7 @@ async function runAct(userPrompt, opts) {
770
814
  }
771
815
  sp.stop();
772
816
  emVoo = null;
817
+ tempos.somar(subagent ? 'rodada do subagente' : 'rodada do modelo', Date.now() - tRodada);
773
818
  if (PARAR) { if (fluxo) fluxo.fechar(); break; }
774
819
 
775
820
  // Chamada de função malformada: o modelo errou a SINTAXE da chamada,
@@ -946,6 +991,8 @@ async function runAct(userPrompt, opts) {
946
991
 
947
992
  let finished = null;
948
993
  let algoRodou = false;
994
+ const tTools = Date.now();
995
+ const conferindoAntes = msConferindo;
949
996
 
950
997
  // ORQUESTRAÇÃO: subagentes da MESMA rodada rodam juntos. O modelo que
951
998
  // pede "3 agentes, um por arquivo" esperava paralelismo e recebia uma
@@ -962,7 +1009,7 @@ async function runAct(userPrompt, opts) {
962
1009
  fala.paralelo(juntos.length, profundidade);
963
1010
  const rs = await Promise.all(juntos.map((tc) =>
964
1011
  runSubagent(tc.arguments || {}, {
965
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1012
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
966
1013
  }).catch((e) => `Subagente falhou: ${e && e.message}`)));
967
1014
  juntos.forEach((tc, i) => resultadosSpawn.set(tc.id, rs[i]));
968
1015
  }
@@ -1005,7 +1052,7 @@ async function runAct(userPrompt, opts) {
1005
1052
  const r = resultadosSpawn.has(tc.id)
1006
1053
  ? resultadosSpawn.get(tc.id)
1007
1054
  : await runSubagent(tc.arguments || {}, {
1008
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1055
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
1009
1056
  });
1010
1057
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1011
1058
  messages.push({ role: 'tool', tool_call_id: tc.id, content: r });
@@ -1018,7 +1065,7 @@ async function runAct(userPrompt, opts) {
1018
1065
  algoRodou = true;
1019
1066
  fala.acao(tc.name, tc.arguments, profundidade);
1020
1067
  const r = await rodarDiretor(tc.arguments || {}, {
1021
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1068
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
1022
1069
  });
1023
1070
  logResult(tc.name, r, profundidade);
1024
1071
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
@@ -1036,7 +1083,7 @@ async function runAct(userPrompt, opts) {
1036
1083
  garantirPasta();
1037
1084
  fala.acao(tc.name, tc.arguments, profundidade);
1038
1085
  const r = await rodarPipeline(tc.arguments || {}, {
1039
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, livro, autorizar, controle: portaoControle,
1086
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes, livro, autorizar, controle: portaoControle,
1040
1087
  });
1041
1088
  logResult(tc.name, r, profundidade);
1042
1089
  repeticao.executou(jaChamado, tc.name, tc.arguments);
@@ -1053,7 +1100,18 @@ async function runAct(userPrompt, opts) {
1053
1100
  const eco = repeticao.ver(jaChamado, tc.name, tc.arguments);
1054
1101
  if (eco.repetida) {
1055
1102
  voltasEmFalso++;
1056
- messages.push({ role: 'tool', tool_call_id: tc.id, content: eco.aviso });
1103
+ /* RELER SEM MUDANÇA devolve o que já foi lido, uma vez.
1104
+ O aviso seco ("o resultado está na conversa acima") mentia
1105
+ depois do `compactar`: a leitura velha já tinha virado 300
1106
+ caracteres, e o modelo gastava OUTRA ida ao servidor para
1107
+ achar o arquivo por outro caminho (um `cat`, um grep). Na
1108
+ segunda vez vai o conteúdo guardado — ler de novo sem nada
1109
+ mudar dá o mesmo texto, então não há o que executar. Da
1110
+ terceira em diante é laço, e o freio segue como era. */
1111
+ const guardado = eco.vezes === 2 && leiturasGuardadas.get(eco.chave);
1112
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: guardado
1113
+ ? `(sem mudança desde a última leitura — é o mesmo conteúdo; não precisa ler de novo)\n${guardado}`
1114
+ : eco.aviso });
1057
1115
  continue;
1058
1116
  }
1059
1117
 
@@ -1080,6 +1138,7 @@ async function runAct(userPrompt, opts) {
1080
1138
  const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
1081
1139
  if (!porta.ok) {
1082
1140
  fala.acao(tc.name, tc.arguments, profundidade);
1141
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: 'não autorizado' });
1083
1142
  console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
1084
1143
  registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
1085
1144
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: porta.error }) });
@@ -1087,12 +1146,40 @@ async function runAct(userPrompt, opts) {
1087
1146
  }
1088
1147
  }
1089
1148
 
1149
+ /* O PORTÃO do que não tem volta (lib/perigo.js). No mesmo lugar do
1150
+ portão do desktop e pelo mesmo motivo: antes, não depois. A
1151
+ resposta mora no mesmo objeto da sessão — quem lembra é ela. */
1152
+ if (!portaoControle.perigo) portaoControle.perigo = perigo.novo();
1153
+ const risco = await perigo.portao(portaoControle.perigo, {
1154
+ modo: permissoes, nome: tc.name, args: tc.arguments,
1155
+ projectDir: pastaAtual, subagent, perguntar: autorizar,
1156
+ });
1157
+ if (!risco.ok) {
1158
+ fala.acao(tc.name, tc.arguments, profundidade);
1159
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: `não autorizado — ${risco.motivo}` });
1160
+ console.log(fala.resultado(profundidade) + c.warn(`não autorizado — ${risco.motivo}`));
1161
+ registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: risco.error });
1162
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: risco.error }) });
1163
+ continue;
1164
+ }
1165
+
1090
1166
  algoRodou = true;
1091
1167
  rodouTool = true;
1092
1168
  fala.acao(tc.name, tc.arguments, profundidade);
1093
- const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual });
1169
+ /* Antes de mexer no arquivo, guarda o original — é o que o /desfazer
1170
+ devolve. Caminho inválido não é problema nosso: a tool recusa. */
1171
+ if (checkpoint.MUDAM_ARQUIVO.has(tc.name)) {
1172
+ try { checkpoint.antesDeMudar(tools.resolveInProject(tc.arguments && tc.arguments.path, pastaAtual), pastaAtual); } catch {}
1173
+ }
1174
+ /* O servidor, o token e o cliente da API vão junto: a legenda do
1175
+ estudio_video transcreve no servidor, e sem eles a ferramenta
1176
+ respondia "a transcrição precisa do servidor". */
1177
+ const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
1094
1178
  logResult(tc.name, result, profundidade);
1095
1179
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1180
+ if (RELEITURA.has(tc.name) && result && result.ok !== false) {
1181
+ leiturasGuardadas.set(eco.chave, summarizeForModel(result));
1182
+ }
1096
1183
  if (tc.name === 'write_todos' && result && Array.isArray(result.todos)) todosDaTarefa = result.todos;
1097
1184
  // Além de narrar, ANOTA: no fim do pedido sai o balanço do que
1098
1185
  // nasceu, mudou, rodou e falhou. Narrar sozinho não basta — numa
@@ -1101,6 +1188,19 @@ async function runAct(userPrompt, opts) {
1101
1188
  if (result && result.ok !== false && catalogo.criaProjeto(tc.name, tc.arguments)
1102
1189
  && /\.(html?|css|m?jsx?|tsx?|vue|svelte)$/i.test(String((tc.arguments && tc.arguments.path) || ''))) {
1103
1190
  mexeuNaPagina = true;
1191
+ /* O NAVEGADOR ESCONDIDO SOBE AGORA, e não no `finish`.
1192
+ A conferência só roda quando o agente diz "pronto", e até
1193
+ lá ainda há pelo menos uma rodada do modelo. Subir o Chrome
1194
+ ali, em paralelo com ela, tira do "pronto" o tempo de ligar
1195
+ o navegador e de fazer a PRIMEIRA navegação dele, que é a
1196
+ parte cara (ver `aquecerRevisor` em lib/browser.js). Medido
1197
+ em 24/09: a conferência num Chrome recém-aberto levava de
1198
+ 2,1 a 4,6s; aquecido antes, 1,24s — que é o assentar de
1199
+ 1,2s, e nada mais. Sem `await` e sem erro: se não subir, a
1200
+ conferência tenta de novo e diz o que houve. */
1201
+ if (aquecerPagina && !subagent && conferencias < 2) {
1202
+ try { Promise.resolve(aquecerPagina()).catch(() => {}); } catch { /* segue */ }
1203
+ }
1104
1204
  }
1105
1205
 
1106
1206
  // Uma escrita bem-sucedida limpa a dívida — mas só a do MESMO
@@ -1142,6 +1242,9 @@ async function runAct(userPrompt, opts) {
1142
1242
  messages.push({ role: 'tool', tool_call_id: tc.id, content: summarizeForModel(result) });
1143
1243
  }
1144
1244
 
1245
+ // A conferência da página tem linha própria: aqui fica só o resto.
1246
+ tempos.somar('ferramentas', Date.now() - tTools - (msConferindo - conferindoAntes));
1247
+
1145
1248
  // Uma volta inteira sem executar nada é o modelo preso. Insistir custa
1146
1249
  // um pedido ao servidor por volta, e as 15 voltas saíam caras para
1147
1250
  // terminar no mesmo lugar — melhor parar e devolver a vez ao usuário.
@@ -1232,7 +1335,7 @@ async function runAct(userPrompt, opts) {
1232
1335
  }
1233
1336
 
1234
1337
  async function runSubagent(args, ctx) {
1235
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1338
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1236
1339
  const descricao = args.description || 'sub-tarefa';
1237
1340
 
1238
1341
  if (profundidade + 1 >= MAX_PROFUNDIDADE) {
@@ -1251,6 +1354,7 @@ async function runSubagent(args, ctx) {
1251
1354
  profundidade: profundidade + 1,
1252
1355
  subagent: true,
1253
1356
  motor,
1357
+ permissoes,
1254
1358
  autorizar: ctx.autorizar,
1255
1359
  controle: ctx.controle, // o mesmo objeto: a resposta vale para todos
1256
1360
  });
@@ -1294,7 +1398,7 @@ async function runSubagent(args, ctx) {
1294
1398
  * inventar — e inventar é onde ele erra.
1295
1399
  */
1296
1400
  async function rodarDiretor(args, ctx) {
1297
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1401
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1298
1402
  if (profundidade > 0) {
1299
1403
  return { ok: false, error: 'Recusado: o diretor não roda dentro de um subagente. '
1300
1404
  + 'Produza com o plano que você já recebeu.' };
@@ -1305,7 +1409,7 @@ async function rodarDiretor(args, ctx) {
1305
1409
  fala.agente('Diretor', rotulo, profundidade);
1306
1410
  const r = await runAct(prompt, {
1307
1411
  server, model, token, projectDir, projectName,
1308
- history: [], profundidade: profundidade + 1, subagent: true, motor,
1412
+ history: [], profundidade: profundidade + 1, subagent: true, motor, permissoes,
1309
1413
  });
1310
1414
  return (r && r.assistantMessage && r.assistantMessage.content) || '';
1311
1415
  };
@@ -1338,7 +1442,7 @@ async function rodarDiretor(args, ctx) {
1338
1442
  }
1339
1443
 
1340
1444
  async function rodarPipeline(args, ctx) {
1341
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1445
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1342
1446
  if (profundidade > 0) {
1343
1447
  return { ok: false, error: 'Recusado: o pipeline não roda dentro de um subagente — '
1344
1448
  + 'os papéis dele já são subagentes. Faça a parte que te cabe.' };
@@ -1361,7 +1465,7 @@ async function rodarPipeline(args, ctx) {
1361
1465
  fala.agente('Agente', rotulo, profundidade);
1362
1466
  const r = await runAct(prompt, {
1363
1467
  server, model, token, projectDir: pasta, projectName,
1364
- history: [], profundidade: profundidade + 1, subagent: true, motor,
1468
+ history: [], profundidade: profundidade + 1, subagent: true, motor, permissoes,
1365
1469
  });
1366
1470
  return (r && r.assistantMessage && r.assistantMessage.content) || '';
1367
1471
  };
@@ -1376,5 +1480,5 @@ async function rodarPipeline(args, ctx) {
1376
1480
  });
1377
1481
  }
1378
1482
 
1379
- module.exports = { runAct, interromper, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1483
+ module.exports = { runAct, buildMemoryContext, interromper, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1380
1484
  anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
package/lib/api.js CHANGED
@@ -268,6 +268,13 @@ function getConectores(serverUrl, token) {
268
268
  return pedirGet(serverUrl, '/api/conectores', token);
269
269
  }
270
270
 
271
+ /* Foto e vídeo de apoio do Pexels, pela chave do servidor (ninguém precisa
272
+ ter uma). tipo: "imagens" | "videos". */
273
+ function getMidia(serverUrl, token, tipo, { q, n, orientacao } = {}) {
274
+ const qs = new URLSearchParams({ q: q || '', n: String(n || 6), ...(orientacao ? { orientacao } : {}) });
275
+ return pedirGet(serverUrl, `/api/midia/${tipo}?${qs}`, token);
276
+ }
277
+
271
278
  function getHealth(serverUrl, token) {
272
279
  return pedirGet(serverUrl, '/health', token);
273
280
  }
@@ -300,4 +307,4 @@ function pedirGet(serverUrl, rota, token) {
300
307
  });
301
308
  }
302
309
 
303
- module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, requestTranscricao, getHealth, getConectores };
310
+ module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, requestTranscricao, getHealth, getConectores, getMidia };