primocode 9.8.0-beta.0 → 9.8.0-beta.10

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 (64) hide show
  1. package/README.md +36 -8
  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 +770 -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 +118 -18
  12. package/lib/api.js +21 -0
  13. package/lib/app.js +317 -74
  14. package/lib/browser.js +79 -3
  15. package/lib/checkpoint-git.js +218 -0
  16. package/lib/checkpoint.js +249 -0
  17. package/lib/claude-engine.js +630 -0
  18. package/lib/codex-engine.js +207 -0
  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/fala.js +95 -1
  23. package/lib/local/cerebro.json +2116 -0
  24. package/lib/local/entrada.js +332 -0
  25. package/lib/local/motor-cli.js +90 -0
  26. package/lib/local/motor.js +264 -0
  27. package/lib/local/provedor.js +169 -0
  28. package/lib/mencoes.js +98 -0
  29. package/lib/offline.js +168 -0
  30. package/lib/painel.js +291 -0
  31. package/lib/perigo.js +256 -0
  32. package/lib/pre-leitura.js +118 -0
  33. package/lib/repeticao.js +4 -2
  34. package/lib/skills.js +6 -1
  35. package/lib/tempos.js +62 -0
  36. package/lib/ui.js +171 -17
  37. package/package.json +3 -2
  38. package/skills/apresentacao-pptx/SKILL.md +52 -0
  39. package/skills/cartaz-de-arte/LICENSE.txt +202 -0
  40. package/skills/cartaz-de-arte/SKILL.md +47 -0
  41. package/skills/criar-skill/LICENSE.txt +202 -0
  42. package/skills/criar-skill/SKILL.md +49 -0
  43. package/skills/depurar-de-verdade/LICENSE.txt +21 -0
  44. package/skills/depurar-de-verdade/SKILL.md +14 -5
  45. package/skills/design-de-verdade/LICENSE.txt +177 -0
  46. package/skills/design-de-verdade/SKILL.md +42 -0
  47. package/skills/documento-word/SKILL.md +57 -0
  48. package/skills/interface-revisada/SKILL.md +47 -0
  49. package/skills/me-ensina/LICENSE.txt +21 -0
  50. package/skills/me-ensina/SKILL.md +27 -0
  51. package/skills/me-entrevista/LICENSE.txt +21 -0
  52. package/skills/me-entrevista/SKILL.md +27 -0
  53. package/skills/motion-de-primeira/SKILL.md +1 -1
  54. package/skills/pdf-bonito/SKILL.md +48 -0
  55. package/skills/planilha-excel/SKILL.md +47 -0
  56. package/skills/react-sem-dor/SKILL.md +48 -0
  57. package/skills/revisao-antes-de-entregar/LICENSE.txt +21 -0
  58. package/skills/revisao-antes-de-entregar/SKILL.md +6 -1
  59. package/skills/site-com-motion/SKILL.md +39 -0
  60. package/skills/temas-prontos/LICENSE.txt +202 -0
  61. package/skills/temas-prontos/SKILL.md +50 -0
  62. package/skills/testar-o-site/LICENSE.txt +202 -0
  63. package/skills/testar-o-site/SKILL.md +46 -0
  64. package/skills/video-no-remotion/SKILL.md +48 -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 });
@@ -724,6 +763,7 @@ async function runAct(userPrompt, opts) {
724
763
  let fluxo = null;
725
764
 
726
765
  emVoo = new AbortController();
766
+ const tRodada = Date.now();
727
767
  try {
728
768
  await motor.requestAct(server, { messages: compactar(messages), model, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
729
769
  if (evt.erro) { streamError = evt.erro; return; }
@@ -770,6 +810,7 @@ async function runAct(userPrompt, opts) {
770
810
  }
771
811
  sp.stop();
772
812
  emVoo = null;
813
+ tempos.somar(subagent ? 'rodada do subagente' : 'rodada do modelo', Date.now() - tRodada);
773
814
  if (PARAR) { if (fluxo) fluxo.fechar(); break; }
774
815
 
775
816
  // Chamada de função malformada: o modelo errou a SINTAXE da chamada,
@@ -946,6 +987,8 @@ async function runAct(userPrompt, opts) {
946
987
 
947
988
  let finished = null;
948
989
  let algoRodou = false;
990
+ const tTools = Date.now();
991
+ const conferindoAntes = msConferindo;
949
992
 
950
993
  // ORQUESTRAÇÃO: subagentes da MESMA rodada rodam juntos. O modelo que
951
994
  // pede "3 agentes, um por arquivo" esperava paralelismo e recebia uma
@@ -962,7 +1005,7 @@ async function runAct(userPrompt, opts) {
962
1005
  fala.paralelo(juntos.length, profundidade);
963
1006
  const rs = await Promise.all(juntos.map((tc) =>
964
1007
  runSubagent(tc.arguments || {}, {
965
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1008
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
966
1009
  }).catch((e) => `Subagente falhou: ${e && e.message}`)));
967
1010
  juntos.forEach((tc, i) => resultadosSpawn.set(tc.id, rs[i]));
968
1011
  }
@@ -1005,7 +1048,7 @@ async function runAct(userPrompt, opts) {
1005
1048
  const r = resultadosSpawn.has(tc.id)
1006
1049
  ? resultadosSpawn.get(tc.id)
1007
1050
  : await runSubagent(tc.arguments || {}, {
1008
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1051
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
1009
1052
  });
1010
1053
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1011
1054
  messages.push({ role: 'tool', tool_call_id: tc.id, content: r });
@@ -1018,7 +1061,7 @@ async function runAct(userPrompt, opts) {
1018
1061
  algoRodou = true;
1019
1062
  fala.acao(tc.name, tc.arguments, profundidade);
1020
1063
  const r = await rodarDiretor(tc.arguments || {}, {
1021
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
1064
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
1022
1065
  });
1023
1066
  logResult(tc.name, r, profundidade);
1024
1067
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
@@ -1036,7 +1079,7 @@ async function runAct(userPrompt, opts) {
1036
1079
  garantirPasta();
1037
1080
  fala.acao(tc.name, tc.arguments, profundidade);
1038
1081
  const r = await rodarPipeline(tc.arguments || {}, {
1039
- server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, livro, autorizar, controle: portaoControle,
1082
+ server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes, livro, autorizar, controle: portaoControle,
1040
1083
  });
1041
1084
  logResult(tc.name, r, profundidade);
1042
1085
  repeticao.executou(jaChamado, tc.name, tc.arguments);
@@ -1053,7 +1096,18 @@ async function runAct(userPrompt, opts) {
1053
1096
  const eco = repeticao.ver(jaChamado, tc.name, tc.arguments);
1054
1097
  if (eco.repetida) {
1055
1098
  voltasEmFalso++;
1056
- messages.push({ role: 'tool', tool_call_id: tc.id, content: eco.aviso });
1099
+ /* RELER SEM MUDANÇA devolve o que já foi lido, uma vez.
1100
+ O aviso seco ("o resultado está na conversa acima") mentia
1101
+ depois do `compactar`: a leitura velha já tinha virado 300
1102
+ caracteres, e o modelo gastava OUTRA ida ao servidor para
1103
+ achar o arquivo por outro caminho (um `cat`, um grep). Na
1104
+ segunda vez vai o conteúdo guardado — ler de novo sem nada
1105
+ mudar dá o mesmo texto, então não há o que executar. Da
1106
+ terceira em diante é laço, e o freio segue como era. */
1107
+ const guardado = eco.vezes === 2 && leiturasGuardadas.get(eco.chave);
1108
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: guardado
1109
+ ? `(sem mudança desde a última leitura — é o mesmo conteúdo; não precisa ler de novo)\n${guardado}`
1110
+ : eco.aviso });
1057
1111
  continue;
1058
1112
  }
1059
1113
 
@@ -1080,6 +1134,7 @@ async function runAct(userPrompt, opts) {
1080
1134
  const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
1081
1135
  if (!porta.ok) {
1082
1136
  fala.acao(tc.name, tc.arguments, profundidade);
1137
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: 'não autorizado' });
1083
1138
  console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
1084
1139
  registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
1085
1140
  messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: porta.error }) });
@@ -1087,12 +1142,40 @@ async function runAct(userPrompt, opts) {
1087
1142
  }
1088
1143
  }
1089
1144
 
1145
+ /* O PORTÃO do que não tem volta (lib/perigo.js). No mesmo lugar do
1146
+ portão do desktop e pelo mesmo motivo: antes, não depois. A
1147
+ resposta mora no mesmo objeto da sessão — quem lembra é ela. */
1148
+ if (!portaoControle.perigo) portaoControle.perigo = perigo.novo();
1149
+ const risco = await perigo.portao(portaoControle.perigo, {
1150
+ modo: permissoes, nome: tc.name, args: tc.arguments,
1151
+ projectDir: pastaAtual, subagent, perguntar: autorizar,
1152
+ });
1153
+ if (!risco.ok) {
1154
+ fala.acao(tc.name, tc.arguments, profundidade);
1155
+ fala.avisar('resultado', { nome: tc.name, ok: false, resumo: `não autorizado — ${risco.motivo}` });
1156
+ console.log(fala.resultado(profundidade) + c.warn(`não autorizado — ${risco.motivo}`));
1157
+ registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: risco.error });
1158
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: risco.error }) });
1159
+ continue;
1160
+ }
1161
+
1090
1162
  algoRodou = true;
1091
1163
  rodouTool = true;
1092
1164
  fala.acao(tc.name, tc.arguments, profundidade);
1093
- const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual });
1165
+ /* Antes de mexer no arquivo, guarda o original — é o que o /desfazer
1166
+ devolve. Caminho inválido não é problema nosso: a tool recusa. */
1167
+ if (checkpoint.MUDAM_ARQUIVO.has(tc.name)) {
1168
+ try { checkpoint.antesDeMudar(tools.resolveInProject(tc.arguments && tc.arguments.path, pastaAtual), pastaAtual); } catch {}
1169
+ }
1170
+ /* O servidor, o token e o cliente da API vão junto: a legenda do
1171
+ estudio_video transcreve no servidor, e sem eles a ferramenta
1172
+ respondia "a transcrição precisa do servidor". */
1173
+ const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
1094
1174
  logResult(tc.name, result, profundidade);
1095
1175
  repeticao.executou(jaChamado, tc.name, tc.arguments);
1176
+ if (RELEITURA.has(tc.name) && result && result.ok !== false) {
1177
+ leiturasGuardadas.set(eco.chave, summarizeForModel(result));
1178
+ }
1096
1179
  if (tc.name === 'write_todos' && result && Array.isArray(result.todos)) todosDaTarefa = result.todos;
1097
1180
  // Além de narrar, ANOTA: no fim do pedido sai o balanço do que
1098
1181
  // nasceu, mudou, rodou e falhou. Narrar sozinho não basta — numa
@@ -1101,6 +1184,19 @@ async function runAct(userPrompt, opts) {
1101
1184
  if (result && result.ok !== false && catalogo.criaProjeto(tc.name, tc.arguments)
1102
1185
  && /\.(html?|css|m?jsx?|tsx?|vue|svelte)$/i.test(String((tc.arguments && tc.arguments.path) || ''))) {
1103
1186
  mexeuNaPagina = true;
1187
+ /* O NAVEGADOR ESCONDIDO SOBE AGORA, e não no `finish`.
1188
+ A conferência só roda quando o agente diz "pronto", e até
1189
+ lá ainda há pelo menos uma rodada do modelo. Subir o Chrome
1190
+ ali, em paralelo com ela, tira do "pronto" o tempo de ligar
1191
+ o navegador e de fazer a PRIMEIRA navegação dele, que é a
1192
+ parte cara (ver `aquecerRevisor` em lib/browser.js). Medido
1193
+ em 24/09: a conferência num Chrome recém-aberto levava de
1194
+ 2,1 a 4,6s; aquecido antes, 1,24s — que é o assentar de
1195
+ 1,2s, e nada mais. Sem `await` e sem erro: se não subir, a
1196
+ conferência tenta de novo e diz o que houve. */
1197
+ if (aquecerPagina && !subagent && conferencias < 2) {
1198
+ try { Promise.resolve(aquecerPagina()).catch(() => {}); } catch { /* segue */ }
1199
+ }
1104
1200
  }
1105
1201
 
1106
1202
  // Uma escrita bem-sucedida limpa a dívida — mas só a do MESMO
@@ -1142,6 +1238,9 @@ async function runAct(userPrompt, opts) {
1142
1238
  messages.push({ role: 'tool', tool_call_id: tc.id, content: summarizeForModel(result) });
1143
1239
  }
1144
1240
 
1241
+ // A conferência da página tem linha própria: aqui fica só o resto.
1242
+ tempos.somar('ferramentas', Date.now() - tTools - (msConferindo - conferindoAntes));
1243
+
1145
1244
  // Uma volta inteira sem executar nada é o modelo preso. Insistir custa
1146
1245
  // um pedido ao servidor por volta, e as 15 voltas saíam caras para
1147
1246
  // terminar no mesmo lugar — melhor parar e devolver a vez ao usuário.
@@ -1232,7 +1331,7 @@ async function runAct(userPrompt, opts) {
1232
1331
  }
1233
1332
 
1234
1333
  async function runSubagent(args, ctx) {
1235
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1334
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1236
1335
  const descricao = args.description || 'sub-tarefa';
1237
1336
 
1238
1337
  if (profundidade + 1 >= MAX_PROFUNDIDADE) {
@@ -1251,6 +1350,7 @@ async function runSubagent(args, ctx) {
1251
1350
  profundidade: profundidade + 1,
1252
1351
  subagent: true,
1253
1352
  motor,
1353
+ permissoes,
1254
1354
  autorizar: ctx.autorizar,
1255
1355
  controle: ctx.controle, // o mesmo objeto: a resposta vale para todos
1256
1356
  });
@@ -1294,7 +1394,7 @@ async function runSubagent(args, ctx) {
1294
1394
  * inventar — e inventar é onde ele erra.
1295
1395
  */
1296
1396
  async function rodarDiretor(args, ctx) {
1297
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1397
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1298
1398
  if (profundidade > 0) {
1299
1399
  return { ok: false, error: 'Recusado: o diretor não roda dentro de um subagente. '
1300
1400
  + 'Produza com o plano que você já recebeu.' };
@@ -1305,7 +1405,7 @@ async function rodarDiretor(args, ctx) {
1305
1405
  fala.agente('Diretor', rotulo, profundidade);
1306
1406
  const r = await runAct(prompt, {
1307
1407
  server, model, token, projectDir, projectName,
1308
- history: [], profundidade: profundidade + 1, subagent: true, motor,
1408
+ history: [], profundidade: profundidade + 1, subagent: true, motor, permissoes,
1309
1409
  });
1310
1410
  return (r && r.assistantMessage && r.assistantMessage.content) || '';
1311
1411
  };
@@ -1338,7 +1438,7 @@ async function rodarDiretor(args, ctx) {
1338
1438
  }
1339
1439
 
1340
1440
  async function rodarPipeline(args, ctx) {
1341
- const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
1441
+ const { server, model, token, projectDir, projectName, profundidade, motor, permissoes } = ctx;
1342
1442
  if (profundidade > 0) {
1343
1443
  return { ok: false, error: 'Recusado: o pipeline não roda dentro de um subagente — '
1344
1444
  + 'os papéis dele já são subagentes. Faça a parte que te cabe.' };
@@ -1361,7 +1461,7 @@ async function rodarPipeline(args, ctx) {
1361
1461
  fala.agente('Agente', rotulo, profundidade);
1362
1462
  const r = await runAct(prompt, {
1363
1463
  server, model, token, projectDir: pasta, projectName,
1364
- history: [], profundidade: profundidade + 1, subagent: true, motor,
1464
+ history: [], profundidade: profundidade + 1, subagent: true, motor, permissoes,
1365
1465
  });
1366
1466
  return (r && r.assistantMessage && r.assistantMessage.content) || '';
1367
1467
  };
@@ -1376,5 +1476,5 @@ async function rodarPipeline(args, ctx) {
1376
1476
  });
1377
1477
  }
1378
1478
 
1379
- module.exports = { runAct, interromper, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1479
+ module.exports = { runAct, buildMemoryContext, interromper, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
1380
1480
  anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
package/lib/api.js CHANGED
@@ -201,14 +201,31 @@ function requestStream(serverUrl, endpoint, payload, onEvent, token, sinal) {
201
201
  });
202
202
  }
203
203
 
204
+ /* Com um provedor configurado (/api), estas quatro rotas rodam AQUI,
205
+ com a chave da pessoa, em vez de ir ao servidor — ver lib/local/motor.js.
206
+ O resto (login, transcrição, conectores) continua no servidor. */
207
+ function local() {
208
+ try {
209
+ const atual = require('./local/provedor.js').atual();
210
+ if (!atual) return null;
211
+ // Claude Code e Codex: a ferramenta de fora é o cérebro.
212
+ if (atual.cli) return require('./local/motor-cli.js').criar(atual.id);
213
+ return require('./local/motor.js');
214
+ } catch { return null; }
215
+ }
216
+
204
217
  // Agente autônomo. `payload` carrega a conversa completa (messages).
205
218
  function requestAct(serverUrl, payload, onEvent) {
219
+ const m = local();
220
+ if (m) return m.requestAct(serverUrl, payload, onEvent);
206
221
  const { token, sinal, ...body } = payload;
207
222
  return requestStream(serverUrl, '/api/act/stream', body, onEvent, token, sinal);
208
223
  }
209
224
 
210
225
  // Chat puro.
211
226
  function requestChat(serverUrl, payload, onEvent) {
227
+ const m = local();
228
+ if (m) return m.requestChat(serverUrl, payload, onEvent);
212
229
  const { token, sinal, ...body } = payload;
213
230
  return requestStream(serverUrl, '/api/chat/stream', body, onEvent, token, sinal);
214
231
  }
@@ -216,6 +233,8 @@ function requestChat(serverUrl, payload, onEvent) {
216
233
  // Resumo incremental da conversa. Vai e volta pequeno: manda o resumo de
217
234
  // antes + o que é novo, recebe o resumo atualizado.
218
235
  function requestResumo(serverUrl, payload) {
236
+ const m = local();
237
+ if (m) return m.requestResumo(serverUrl, payload);
219
238
  const { token, ...body } = payload;
220
239
  return request(serverUrl, '/api/resumir', body, token);
221
240
  }
@@ -236,6 +255,8 @@ function requestRefino(serverUrl, payload) {
236
255
 
237
256
  // O plano: a tarefa grande dividida em passos, para uma chamada por pedaço.
238
257
  function requestPlano(serverUrl, payload) {
258
+ const m = local();
259
+ if (m) return m.requestPlano(serverUrl, payload);
239
260
  const { token, ...body } = payload;
240
261
  return request(serverUrl, '/api/plano', body, token);
241
262
  }