primocode 10.5.0 → 10.7.0

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 (44) hide show
  1. package/README.md +2 -2
  2. package/bin/primocode.js +117 -24
  3. package/lib/act.js +141 -9
  4. package/lib/agents-md.js +140 -0
  5. package/lib/auditoria.js +122 -0
  6. package/lib/browser.js +48 -1
  7. package/lib/catalogo.js +29 -6
  8. package/lib/comandos-extra.js +2 -0
  9. package/lib/config.js +32 -4
  10. package/lib/entrada.js +33 -11
  11. package/lib/estudio/cenarios.js +91 -0
  12. package/lib/estudio/colecao.js +35 -1
  13. package/lib/estudio/diretor.js +1 -0
  14. package/lib/estudio/falantes.js +30 -6
  15. package/lib/estudio/hibrido.js +144 -8
  16. package/lib/estudio/index.js +196 -63
  17. package/lib/estudio/kit/mundo.jsx +1060 -0
  18. package/lib/estudio/kit/voxel.jsx +315 -70
  19. package/lib/estudio/kit.js +1 -1
  20. package/lib/estudio/kokoro.js +18 -4
  21. package/lib/estudio/margem.js +1 -1
  22. package/lib/estudio/movimentos.js +212 -0
  23. package/lib/estudio/mundo.js +328 -0
  24. package/lib/estudio/personagem.js +164 -11
  25. package/lib/estudio/plano.js +6 -0
  26. package/lib/estudio/producao.js +107 -17
  27. package/lib/estudio/projeto.js +77 -22
  28. package/lib/leitura.js +176 -0
  29. package/lib/local/cerebro.json +180 -3
  30. package/lib/local/entrada.js +61 -4
  31. package/lib/mcp.js +296 -12
  32. package/lib/memoria-conversa.js +32 -3
  33. package/lib/mencoes.js +5 -1
  34. package/lib/nome-curto.js +126 -0
  35. package/lib/nome-projeto.js +5 -2
  36. package/lib/outra-pasta.js +161 -0
  37. package/lib/perigo.js +34 -2
  38. package/lib/referencia.js +40 -11
  39. package/lib/repeticao.js +20 -1
  40. package/lib/tela.js +1 -1
  41. package/lib/tools.js +76 -6
  42. package/lib/travado.js +141 -0
  43. package/lib/ui.js +1 -0
  44. package/package.json +2 -2
package/lib/leitura.js ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * leitura.js — ler uma página FORA do modelo, em cascata, com teste de tamanho.
3
+ *
4
+ * Ideia levada do `ax` (yusukebe/ax, "curl pra agente"): o modelo não precisa
5
+ * do HTML, precisa da ESTRUTURA — título, subtítulos, o texto principal, os
6
+ * links. Extrair isso aqui, de graça e de forma determinística, poupa a janela
7
+ * de contexto de um modelo gratuito, que é curta.
8
+ *
9
+ * A cascata para no primeiro passo que basta:
10
+ * 1. busca simples + extração local (sem navegador, sem modelo);
11
+ * 2. se saiu pouco texto (página que só existe depois do JavaScript), o
12
+ * navegador escondido de lib/browser.js renderiza e a extração roda de novo;
13
+ * 3. o que vai pro modelo cabe num orçamento de caracteres: corta por seção,
14
+ * e os subtítulos ficam todos — o modelo sabe o que existe na página
15
+ * mesmo quando o texto de uma seção não coube.
16
+ *
17
+ * A regra que vale mais que a ferramenta: o teste de "leu" é o TAMANHO do que
18
+ * saiu, não o status 200. Página que devolve 85 caracteres "deu certo" e não
19
+ * leu nada.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ // 400, o mesmo piso da pesquisa: casca de SPA deixa 200-300 caracteres de menu.
25
+ const MINIMO = 400;
26
+ const ORCAMENTO = 4500;
27
+ const MAX_LINKS = 12;
28
+
29
+ const ENTIDADES = { nbsp: ' ', amp: '&', lt: '<', gt: '>', quot: '"', '#39': "'", apos: "'" };
30
+ function desentidade(s) {
31
+ return String(s || '')
32
+ .replace(/&(nbsp|amp|lt|gt|quot|#39|apos);/g, (_, k) => ENTIDADES[k])
33
+ .replace(/&#(\d+);/g, (_, n) => { try { return String.fromCodePoint(Number(n)); } catch { return ' '; } });
34
+ }
35
+ const limpo = (s) => desentidade(String(s || '').replace(/<[^>]+>/g, ' ')).replace(/\s+/g, ' ').trim();
36
+
37
+ function atributo(tag, nome) {
38
+ const m = tag.match(new RegExp(`\\b${nome}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i'));
39
+ return m ? desentidade(m[2] ?? m[3] ?? m[4] ?? '') : '';
40
+ }
41
+
42
+ /** O conteúdo da primeira tag `nome` que tiver texto de verdade (main, article). */
43
+ function regiao(html, nome) {
44
+ const re = new RegExp(`<${nome}\\b[^>]*>([\\s\\S]*?)<\\/${nome}>`, 'gi');
45
+ let m;
46
+ while ((m = re.exec(html))) if (limpo(m[1]).length >= 200) return m[1];
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * HTML → estrutura legível. Sem dependência: expressão regular basta para o
52
+ * que o modelo precisa (não é um parser, e não tenta ser).
53
+ * @returns {{titulo:string, descricao:string, secoes:Array<{titulo:string,texto:string}>, texto:string, links:Array<{texto:string,url:string}>}}
54
+ */
55
+ function estruturaDe(html, base) {
56
+ const bruto = String(html || '').replace(/<!--[\s\S]*?-->/g, ' ')
57
+ .replace(/<(script|style|noscript|svg|template|iframe)\b[\s\S]*?<\/\1>/gi, ' ');
58
+ const titulo = limpo((bruto.match(/<title[^>]*>([\s\S]*?)<\/title>/i) || [])[1]);
59
+ const meta = (bruto.match(/<meta\b[^>]*(name|property)\s*=\s*["'](description|og:description)["'][^>]*>/i) || [])[0] || '';
60
+ const descricao = atributo(meta, 'content').trim();
61
+
62
+ // O miolo: <main>, senão <article>, senão o <body> sem menu, rodapé e lateral.
63
+ let corpo = regiao(bruto, 'main') || regiao(bruto, 'article')
64
+ || ((bruto.match(/<body\b[^>]*>([\s\S]*)<\/body>/i) || [])[1] ?? bruto);
65
+ corpo = corpo.replace(/<(nav|footer|header|aside)\b[\s\S]*?<\/\1>/gi, ' ');
66
+
67
+ const links = [];
68
+ const vistos = new Set();
69
+ corpo.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_, attrs, dentro) => {
70
+ const href = atributo(attrs, 'href');
71
+ const texto = limpo(dentro);
72
+ if (!href || !texto || /^(#|javascript:|mailto:)/i.test(href) || links.length >= MAX_LINKS) return '';
73
+ let url = href;
74
+ try { url = new URL(href, base || undefined).href; } catch { /* fica relativo */ }
75
+ if (!vistos.has(url)) { vistos.add(url); links.push({ texto: texto.slice(0, 80), url }); }
76
+ return '';
77
+ });
78
+
79
+ // Subtítulo vira marcador; bloco vira quebra de linha; o resto some.
80
+ const marcado = corpo
81
+ .replace(/<h([1-3])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_, n, t) => `\n\u0001${limpo(t)}\n`)
82
+ .replace(/<(br|\/p|\/li|\/div|\/section|\/tr|\/h[4-6]|\/pre|\/blockquote)\b[^>]*>/gi, '\n')
83
+ .replace(/<li\b[^>]*>/gi, '\n- ')
84
+ .replace(/<[^>]+>/g, ' ');
85
+ const secoes = [{ titulo: '', texto: '' }];
86
+ for (const linhaCrua of desentidade(marcado).split('\n')) {
87
+ const linha = linhaCrua.replace(/\s+/g, ' ').trim();
88
+ if (!linha || linha === '-') continue;
89
+ if (linha.startsWith('\u0001')) secoes.push({ titulo: linha.slice(1).trim(), texto: '' });
90
+ else secoes[secoes.length - 1].texto += (secoes[secoes.length - 1].texto ? '\n' : '') + linha;
91
+ }
92
+ const uteis = secoes.filter((s) => s.titulo || s.texto);
93
+ const texto = uteis.map((s) => [s.titulo, s.texto].filter(Boolean).join('\n')).join('\n');
94
+ return { titulo, descricao, secoes: uteis, texto, links };
95
+ }
96
+
97
+ function cortar(texto, max) {
98
+ if (texto.length <= max) return texto;
99
+ if (max < 20) return '';
100
+ const corte = texto.slice(0, max - 1);
101
+ const espaco = corte.lastIndexOf(' ');
102
+ return (espaco > max * 0.6 ? corte.slice(0, espaco) : corte) + '…';
103
+ }
104
+
105
+ /**
106
+ * Passo 3: o que vai pro modelo cabe em `orcamento` caracteres. Os subtítulos
107
+ * ficam TODOS; o texto de cada seção divide o que sobra por igual, e seção
108
+ * curta devolve a sobra pras outras (enchimento por nível).
109
+ */
110
+ function caber(est, orcamento = ORCAMENTO) {
111
+ const cabeca = [est.titulo && `# ${est.titulo}`, est.descricao && `> ${est.descricao}`].filter(Boolean).join('\n');
112
+ const listaLinks = est.links.length
113
+ ? '\nLinks:\n' + est.links.map((l) => `- ${l.texto} — ${l.url}`).join('\n') : '';
114
+ const linksCabem = cortar(listaLinks, Math.floor(orcamento * 0.15));
115
+ const titulos = est.secoes.reduce((n, s) => n + (s.titulo ? s.titulo.length + 5 : 0) + 1, 0);
116
+ let livre = Math.max(0, orcamento - cabeca.length - linksCabem.length - titulos - 2);
117
+
118
+ const cotas = new Array(est.secoes.length).fill(0);
119
+ let pendentes = est.secoes.map((_, i) => i).filter((i) => est.secoes[i].texto);
120
+ while (pendentes.length && livre > 0) {
121
+ const parte = Math.floor(livre / pendentes.length);
122
+ const curtas = pendentes.filter((i) => est.secoes[i].texto.length <= parte);
123
+ if (!curtas.length) { pendentes.forEach((i) => { cotas[i] = parte; }); break; }
124
+ curtas.forEach((i) => { cotas[i] = est.secoes[i].texto.length; livre -= cotas[i]; });
125
+ pendentes = pendentes.filter((i) => !curtas.includes(i));
126
+ }
127
+ let cortado = linksCabem.length < listaLinks.length;
128
+ const corpo = est.secoes.map((s, i) => {
129
+ const t = cortar(s.texto, cotas[i]);
130
+ if (t.length < s.texto.length) cortado = true;
131
+ return [s.titulo && `## ${s.titulo}`, t].filter(Boolean).join('\n');
132
+ }).join('\n');
133
+ const conteudo = [cabeca, corpo].filter(Boolean).join('\n\n') + linksCabem;
134
+ // A cerca final: nenhum caminho passa do orçamento.
135
+ return { conteudo: conteudo.slice(0, orcamento), cortado: cortado || conteudo.length > orcamento };
136
+ }
137
+
138
+ const bastante = (est, minimo) => !!est && est.texto.length >= minimo;
139
+
140
+ /**
141
+ * A cascata inteira. `pegar(url)` devolve o HTML da busca simples; `navegador(url)`
142
+ * devolve o HTML depois do JavaScript (opcional). `html` pula o passo 1 de rede
143
+ * quando quem chama já baixou a página.
144
+ * @returns {Promise<{ok:boolean, passo:number, como:string, chars:number, enviados:number, cortado:boolean, conteudo:string, titulo:string, texto:string}>}
145
+ */
146
+ async function ler(url, { pegar, navegador, html, orcamento = ORCAMENTO, minimo = MINIMO } = {}) {
147
+ const monta = (passo, como, est) => {
148
+ const { conteudo, cortado } = caber(est, orcamento);
149
+ return { ok: true, url, passo, como, chars: est.texto.length, enviados: conteudo.length,
150
+ cortado, conteudo, titulo: est.titulo, texto: est.texto };
151
+ };
152
+ let e1 = null;
153
+ let semScript = false;
154
+ try {
155
+ const h = html != null ? html : (pegar ? await pegar(url) : null);
156
+ if (h) { e1 = estruturaDe(h, url); semScript = !/<script\b/i.test(h); }
157
+ } catch { /* passo 1 falhou: o navegador tenta */ }
158
+ if (bastante(e1, minimo)) return monta(1, 'busca simples', e1);
159
+
160
+ // Página sem nenhum <script> é curta de verdade: o navegador não acharia mais nada.
161
+ let e2 = null;
162
+ if (navegador && !semScript) {
163
+ try {
164
+ const h = await navegador(url);
165
+ if (h) e2 = estruturaDe(h, url);
166
+ } catch { /* sem navegador: fica o que o passo 1 trouxe */ }
167
+ if (bastante(e2, minimo)) return monta(2, 'navegador (a página precisa de JavaScript)', e2);
168
+ }
169
+ const melhor = [e1, e2].filter(Boolean).sort((a, b) => b.texto.length - a.texto.length)[0];
170
+ return { ok: false, url, passo: e2 ? 2 : 1, como: 'nenhum passo leu o bastante',
171
+ chars: melhor ? melhor.texto.length : 0, enviados: 0, cortado: false, conteudo: '',
172
+ titulo: melhor ? melhor.titulo : '', texto: melhor ? melhor.texto : '',
173
+ error: `a página deu ${melhor ? melhor.texto.length : 0} caracteres de texto (o mínimo é ${minimo})` };
174
+ }
175
+
176
+ module.exports = { ler, estruturaDe, caber, MINIMO, ORCAMENTO };
@@ -1,12 +1,14 @@
1
1
  {
2
- "origem": "primocode-server 8.18.0 437cba2",
2
+ "origem": "primocode-server 8.20.0 7441170",
3
3
  "SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome); arquivo grande → read_file com offset/limit; servidor que fica de pé →\nrun_in_new_terminal.\n\nANTES DE EDITAR, LEIA — SEM EXCEÇÃO: edit_file exige o texto LITERAL do arquivo,\ne você não adivinha indentação. read_file (ou grep) PRIMEIRO, edit_file depois.\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — leia antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ver o que existe (list_dir/glob) → entender (read_file/grep) → fazer\n(write_file/edit_file) → conferir → finish.",
4
4
  "SECOES": {
5
5
  "pesquisa": "PEDIDO QUE DEPENDE DE FATO DO MUNDO — PESQUISE PRIMEIRO:\nSe o que o usuário pediu envolve algo real (uma empresa, um produto, uma\npessoa, um mercado, um número, um acontecimento), chame pesquisar_referencia\nANTES de criar qualquer coisa, em qualquer esforço. \"Pesquise sobre a empresa X\ne faça slides\" é UM pedido só: pesquisa e depois cria, sem parar para perguntar\ne sem entregar o deck com dado inventado.\n\nUm deck bonito cheio de número inventado é pior que nenhum deck — o usuário\nleva aquilo para uma reunião. Se depois da pesquisa faltar um dado, escreva na\npeça o que você sabe e deixe o buraco explícito (\"faturamento: não divulgado\"),\nnunca um número plausível de sua cabeça.",
6
- "estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO (\"edita meu vídeo\", um .mp4/.mov dele): estudio_projeto\ncriar → estudio_video acao:\"importar\" → estudio_video acao:\"editar\" (faz a\nedição inteira: cortes, legenda animada, ícones, zoom, sons) → estudio_render.\nNão monte as cenas à mão antes de tentar o \"editar\". Depois, ajuste só a cena\nque o usuário pedir (estudio_plano cena:N). NUNCA escreva .mp4/.mp3 com\nwrite_file e nunca monte vídeo com ffmpeg na mão.\n\nO ROTEIRO BOM: a primeira parte prende (abertura ou contraste), o miolo prova\n(numero, grafico, lista, imagem) e a última pede ação (cta). Varie os tipos —\ntrês \"titulo\" seguidos parecem slide parado. Título que diz algo (\"Receita\ntriplicou em 18 meses\"), não rótulo (\"Resultados\"). Número de verdade ou\nnenhum. A fala não repete a tela: a tela dá o título, a voz dá o argumento.\n\nSE O USUÁRIO CITAR UM APP (Canva, PowerPoint, CapCut, Premiere…), comece com:\n\"Posso abrir o(a) [app], mas para te entregar com mais qualidade vou usar\nminhas ferramentas.\" — avisa, não pergunta. Se ele insistir, desktop_open_app.\n\nDocumento, planilha e PDF não são do Estúdio: faça o arquivo (.docx, .xlsx,\n.pdf) com código, na pasta do projeto.",
6
+ "estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO (\"edita meu vídeo\", um .mp4/.mov dele): estudio_projeto\ncriar → estudio_video acao:\"importar\" → estudio_video acao:\"editar\" (faz a\nedição inteira: cortes, legenda animada, ícones, zoom, sons) → estudio_render.\nNão monte as cenas à mão antes de tentar o \"editar\". Depois, ajuste só a cena\nque o usuário pedir (estudio_plano cena:N). NUNCA escreva .mp4/.mp3 com\nwrite_file e nunca monte vídeo com ffmpeg na mão.\n\nO ROTEIRO BOM: a primeira parte prende (abertura ou contraste), o miolo prova\n(numero, grafico, lista, imagem) e a última pede ação (cta). Varie os tipos —\ntrês \"titulo\" seguidos parecem slide parado. Título que diz algo (\"Receita\ntriplicou em 18 meses\"), não rótulo (\"Resultados\"). Número de verdade ou\nnenhum. A fala não repete a tela: a tela dá o título, a voz dá o argumento.\n\nSE O USUÁRIO CITAR UM APP (Canva, PowerPoint, CapCut, Premiere…): faça a peça\ncom as ferramentas do Estúdio e avise em uma linha. Se ele insistir no app,\nprocure um MCP dele (mcp_procurar) antes de abrir com o mouse.\n\nDocumento, planilha e PDF não são do Estúdio: faça o arquivo (.docx, .xlsx,\n.pdf) com código, na pasta do projeto.",
7
7
  "navegador": "CONTROLANDO NAVEGADOR E DESKTOP:\nVocê pode AGIR numa página real e no computador inteiro do usuário — qualquer\nprograma, e a própria área de trabalho:\n- Navegador: browser_open abre a página num Chrome controlado; depois\n browser_click (por texto/seletor), browser_type, browser_key, browser_read\n (conferir), browser_screenshot. Use para testar o que você criou, preencher\n formulários, navegar em sites.\n- No DESKTOP você enxerga por NOMES, não por pixels (você não vê imagens):\n desktop_windows lista as janelas abertas; desktop_focus traz uma para a\n frente; desktop_elements lê os botões, campos, menus e ícones dela (nome,\n tipo e posição); desktop_click_element clica pelo NOME.\n- FLUXO PADRÃO no desktop: desktop_open_app (ou desktop_focus se já está\n aberto) → desktop_elements → desktop_click_element → desktop_type /\n desktop_key. Depois de agir, confira: desktop_elements de novo (o estado\n mudou?) ou desktop_windows.\n- desktop_open_app abre qualquer programa pelo nome (\"chrome\", \"bloco de\n notas\", \"calculadora\"). A janela leva 1-2s: confirme com desktop_windows.\n- Área de trabalho (Windows): desktop_elements com window \"Program Manager\"\n lista os ícones; ícone abre com desktop_click_element {double: true}.\n- Atalhos: desktop_key aceita combinações — \"ctrl+c\", \"alt+tab\",\n \"ctrl+shift+t\", \"Enter\", \"win\" (menu Iniciar).\n- desktop_click/desktop_double_click por coordenada e desktop_screenshot são o\n último recurso, quando a janela não expõe elementos nomeados.\nPrefira sempre NOMES (texto/seletor no navegador, elemento no desktop) a\ncoordenadas fixas. Confira o resultado antes de dizer que terminou.\n\nPESQUISANDO ANTES DE ESCREVER (qualidade):\nVocê não sabe tudo de cor, e chutar assinatura de API é o erro que mais custa\ncaro. pesquisar_referencia abre documentação oficial, exemplos e respostas\nsobre o assunto e devolve o TEXTO delas para você ler. Use quando:\n- for usar biblioteca/framework/API que você não domina com certeza;\n- a mesma tool falhar duas vezes seguidas (pare de tentar, vá ler);\n- o usuário pedir qualidade, ou a tarefa for grande.\nLeia, entenda o padrão e ESCREVA o seu código. Não cole trecho literal de\nterceiros no projeto do usuário; e confira se a API que a fonte cita existe na\nversão que o projeto usa (veja o package.json).",
8
- "delegar": "DELEGANDO A ESPECIALISTAS:\nSe a tarefa tem partes independentes, use spawn_agent para cada uma e diga\nQUEM faz em `especialista`: frontend (tela, HTML, CSS) · backend (API, dados\nno servidor, autenticação) · testes (escrever e rodar testes, reproduzir bug)\n· design (identidade visual, paleta, tipografia, ícones) · dados (CSV,\nplanilha, análise) · revisor (lê e aponta defeitos, não conserta) · ambiente\n(instalar dependências, configurar, fazer rodar). Cada um chega com o\nbriefing da profissão. Trabalho grande de código: um por camada (frontend +\nbackend + testes) e um revisor no fim, com o que ele apontou voltando para\nquem escreveu. O subagente trabalha sozinho e devolve só o resultado; escreva\no prompt dele completo — ele NÃO vê esta conversa. Tarefa curta você mesmo\nresolve.",
8
+ "mcp": "FERRAMENTA CERTA: para agir num serviço ou app, use primeiro um MCP (veja\n[mcp]). Sem MCP: mcp_procurar → mcp_conectar → mcp_usar. Mouse/desktop_* só\nquando não houver MCP, API ou CLI. Se o MCP pedir chave, PEÇA ao usuário.",
9
+ "delegar": "DIVIDIR COM ESPECIALISTAS (spawn_agent):\nDELEGUE quando a tarefa tem 2+ partes independentes, mexe em mais de 3\narquivos ou junta pesquisa e construção. Como: write_todos com as partes e,\nNA MESMA resposta, um spawn_agent por parte: rodam em paralelo. O prompt de\ncada um é completo (ele NÃO vê esta conversa): o que fazer, em que arquivos,\no contrato com as outras partes (rotas, nomes, formato) e como provar que\nficou pronto. Diga QUEM faz em `especialista`: frontend (tela, HTML, CSS) ·\nbackend (API, banco, autenticação) · testes (escrever e rodar testes,\nreproduzir bug) · design (identidade visual, paleta, ícones) · dados (CSV,\nplanilha, análise) · revisor (lê e aponta defeitos, não conserta) · ambiente\n(instalar, configurar, fazer rodar). Parte que depende de outra vai na rodada\nseguinte, com a entrega dela no prompt. No fim, um revisor; o que ele apontar\nvolta para quem escreveu.\nNÃO delegue edição pequena (um arquivo, um ajuste): faça você.\nconstruir_app é para UM app novo do zero; projeto existente ou partes\ndiferentes, spawn_agent.",
9
10
  "computador": "CONTROLANDO NAVEGADOR E DESKTOP:\nVocê pode AGIR numa página real e no computador inteiro do usuário — qualquer\nprograma, e a própria área de trabalho:\n- Navegador: browser_open abre a página num Chrome controlado; depois\n browser_click (por texto/seletor), browser_type, browser_key, browser_read\n (conferir), browser_screenshot. Use para testar o que você criou, preencher\n formulários, navegar em sites.\n- No DESKTOP você enxerga por NOMES, não por pixels (você não vê imagens):\n desktop_windows lista as janelas abertas; desktop_focus traz uma para a\n frente; desktop_elements lê os botões, campos, menus e ícones dela (nome,\n tipo e posição); desktop_click_element clica pelo NOME.\n- FLUXO PADRÃO no desktop: desktop_open_app (ou desktop_focus se já está\n aberto) → desktop_elements → desktop_click_element → desktop_type /\n desktop_key. Depois de agir, confira: desktop_elements de novo (o estado\n mudou?) ou desktop_windows.\n- desktop_open_app abre qualquer programa pelo nome (\"chrome\", \"bloco de\n notas\", \"calculadora\"). A janela leva 1-2s: confirme com desktop_windows.\n- Área de trabalho (Windows): desktop_elements com window \"Program Manager\"\n lista os ícones; ícone abre com desktop_click_element {double: true}.\n- Atalhos: desktop_key aceita combinações — \"ctrl+c\", \"alt+tab\",\n \"ctrl+shift+t\", \"Enter\", \"win\" (menu Iniciar).\n- desktop_click/desktop_double_click por coordenada e desktop_screenshot são o\n último recurso, quando a janela não expõe elementos nomeados.\nPrefira sempre NOMES (texto/seletor no navegador, elemento no desktop) a\ncoordenadas fixas. Confira o resultado antes de dizer que terminou.\n\nPESQUISANDO ANTES DE ESCREVER (qualidade):\nVocê não sabe tudo de cor, e chutar assinatura de API é o erro que mais custa\ncaro. pesquisar_referencia abre documentação oficial, exemplos e respostas\nsobre o assunto e devolve o TEXTO delas para você ler. Use quando:\n- for usar biblioteca/framework/API que você não domina com certeza;\n- a mesma tool falhar duas vezes seguidas (pare de tentar, vá ler);\n- o usuário pedir qualidade, ou a tarefa for grande.\nLeia, entenda o padrão e ESCREVA o seu código. Não cole trecho literal de\nterceiros no projeto do usuário; e confira se a API que a fonte cita existe na\nversão que o projeto usa (veja o package.json).",
11
+ "pastas": "OUTRA PASTA: se o pedido fala de outra pasta ou projeto (veja [outra pasta]),\nPROCURE LÁ antes de responder — aqui o caminho ABSOLUTO vale, só para ler:\nread_file/list_dir com ele, glob com padrão absoluto (\"/caminho/**/*.js\"),\ngrep com glob absoluto. Se o trabalho (criar ou mudar arquivo) é LÁ, chame\nmudar_pasta com o motivo — o usuário confirma; recusou, fique onde está.",
10
12
  "exportar": "O ARQUIVO DO VÍDEO: estudio_render já gera o .mp4 (ou o .pdf) na\npasta do projeto e abre para o usuário. Diga o caminho que ele devolve. Para\nrefazer só o arquivo depois de um ajuste, estudio_render de novo com o mesmo id.",
11
13
  "codigo": "QUALIDADE DE CÓDIGO — o que separa código de rascunho:\n- ANTES de escrever num projeto existente, leia um arquivo parecido e siga o\n padrão DELE: nomes, indentação, idioma, como trata erro. Código que destoa\n do projeto é código errado, mesmo funcionando.\n- Estruture: um arquivo por responsabilidade; função passando de ~40 linhas é\n sinal de dividir; nome diz o que a coisa É (calcularTotal, não fazerCoisa).\n- Trate erro DE VERDADE: entrada inválida, arquivo ausente, rede que falha.\n Nada de catch vazio, nada de ignorar retorno de erro.\n- Web: HTML semântico, CSS moderno (grid/flex, variáveis), responsivo de\n celular a desktop, JS sem framework quando vanilla resolve. Visual caprichado\n não é opcional: espaçamento consistente, hierarquia clara, estados de hover.\n- PROVE antes do finish: rode com run_shell (teste, build ou o próprio script)\n ou abra no navegador e olhe o console. Erro no console = tarefa inacabada.\n- NUNCA deixe TODO, \"lorem ipsum\", placeholder ou dado inventado no lugar de\n conteúdo real. Entregue completo ou diga o que faltou e por quê."
12
14
  },
@@ -1076,6 +1078,82 @@
1076
1078
  }
1077
1079
  }
1078
1080
  },
1081
+ {
1082
+ "type": "function",
1083
+ "function": {
1084
+ "name": "mcp_procurar",
1085
+ "description": "Procura o servidor MCP de um servico ou app (lista curada + registro oficial). Devolve como instalar e as chaves que pede.",
1086
+ "parameters": {
1087
+ "type": "object",
1088
+ "properties": {
1089
+ "busca": {
1090
+ "type": "string",
1091
+ "description": "O servico, ex: \"notion\", \"github\""
1092
+ }
1093
+ },
1094
+ "required": [
1095
+ "busca"
1096
+ ]
1097
+ }
1098
+ }
1099
+ },
1100
+ {
1101
+ "type": "function",
1102
+ "function": {
1103
+ "name": "mcp_conectar",
1104
+ "description": "Instala, sobe e valida um servidor MCP (pede autorizacao). Curado: so o nome. Sem a chave obrigatoria nao salva: peca ao usuario.",
1105
+ "parameters": {
1106
+ "type": "object",
1107
+ "properties": {
1108
+ "nome": {
1109
+ "type": "string"
1110
+ },
1111
+ "comando": {
1112
+ "type": "string",
1113
+ "description": "Do \"instalar\" do mcp_procurar"
1114
+ },
1115
+ "args": {
1116
+ "type": "array",
1117
+ "items": {
1118
+ "type": "string"
1119
+ }
1120
+ },
1121
+ "env": {
1122
+ "type": "object",
1123
+ "description": "As chaves, ex {\"NOTION_TOKEN\":\"...\"}"
1124
+ }
1125
+ },
1126
+ "required": [
1127
+ "nome"
1128
+ ]
1129
+ }
1130
+ }
1131
+ },
1132
+ {
1133
+ "type": "function",
1134
+ "function": {
1135
+ "name": "mcp_usar",
1136
+ "description": "Chama uma ferramenta de um servidor MCP conectado.",
1137
+ "parameters": {
1138
+ "type": "object",
1139
+ "properties": {
1140
+ "servidor": {
1141
+ "type": "string"
1142
+ },
1143
+ "ferramenta": {
1144
+ "type": "string"
1145
+ },
1146
+ "argumentos": {
1147
+ "type": "object"
1148
+ }
1149
+ },
1150
+ "required": [
1151
+ "servidor",
1152
+ "ferramenta"
1153
+ ]
1154
+ }
1155
+ }
1156
+ },
1079
1157
  {
1080
1158
  "type": "function",
1081
1159
  "function": {
@@ -1119,6 +1197,29 @@
1119
1197
  }
1120
1198
  }
1121
1199
  },
1200
+ {
1201
+ "type": "function",
1202
+ "function": {
1203
+ "name": "mudar_pasta",
1204
+ "description": "Troca a pasta de trabalho para outra que o usuario citou. O usuario CONFIRMA antes (s/N); se ele recusar, continue na atual. Para so LER la nao precisa trocar: read_file/list_dir com o caminho absoluto, glob com padrao absoluto, grep com glob absoluto.",
1205
+ "parameters": {
1206
+ "type": "object",
1207
+ "properties": {
1208
+ "caminho": {
1209
+ "type": "string",
1210
+ "description": "A pasta: absoluto, ~/..., ou o nome de um projeto"
1211
+ },
1212
+ "motivo": {
1213
+ "type": "string",
1214
+ "description": "Uma frase do porque; aparece na pergunta ao usuario"
1215
+ }
1216
+ },
1217
+ "required": [
1218
+ "caminho"
1219
+ ]
1220
+ }
1221
+ }
1222
+ },
1122
1223
  {
1123
1224
  "type": "function",
1124
1225
  "function": {
@@ -2233,6 +2334,82 @@
2233
2334
  }
2234
2335
  }
2235
2336
  },
2337
+ {
2338
+ "type": "function",
2339
+ "function": {
2340
+ "name": "mcp_procurar",
2341
+ "description": "Procura o servidor MCP de um servico ou app (lista curada + registro oficial). Devolve como instalar e as chaves que pede.",
2342
+ "parameters": {
2343
+ "type": "object",
2344
+ "properties": {
2345
+ "busca": {
2346
+ "type": "string",
2347
+ "description": "O servico, ex: \"notion\", \"github\""
2348
+ }
2349
+ },
2350
+ "required": [
2351
+ "busca"
2352
+ ]
2353
+ }
2354
+ }
2355
+ },
2356
+ {
2357
+ "type": "function",
2358
+ "function": {
2359
+ "name": "mcp_conectar",
2360
+ "description": "Instala, sobe e valida um servidor MCP (pede autorizacao). Curado: so o nome. Sem a chave obrigatoria nao salva: peca ao usuario.",
2361
+ "parameters": {
2362
+ "type": "object",
2363
+ "properties": {
2364
+ "nome": {
2365
+ "type": "string"
2366
+ },
2367
+ "comando": {
2368
+ "type": "string",
2369
+ "description": "Do \"instalar\" do mcp_procurar"
2370
+ },
2371
+ "args": {
2372
+ "type": "array",
2373
+ "items": {
2374
+ "type": "string"
2375
+ }
2376
+ },
2377
+ "env": {
2378
+ "type": "object",
2379
+ "description": "As chaves, ex {\"NOTION_TOKEN\":\"...\"}"
2380
+ }
2381
+ },
2382
+ "required": [
2383
+ "nome"
2384
+ ]
2385
+ }
2386
+ }
2387
+ },
2388
+ {
2389
+ "type": "function",
2390
+ "function": {
2391
+ "name": "mcp_usar",
2392
+ "description": "Chama uma ferramenta de um servidor MCP conectado.",
2393
+ "parameters": {
2394
+ "type": "object",
2395
+ "properties": {
2396
+ "servidor": {
2397
+ "type": "string"
2398
+ },
2399
+ "ferramenta": {
2400
+ "type": "string"
2401
+ },
2402
+ "argumentos": {
2403
+ "type": "object"
2404
+ }
2405
+ },
2406
+ "required": [
2407
+ "servidor",
2408
+ "ferramenta"
2409
+ ]
2410
+ }
2411
+ }
2412
+ },
2236
2413
  {
2237
2414
  "type": "function",
2238
2415
  "function": {
@@ -105,6 +105,22 @@ const GRUPOS = [
105
105
  // (1.141 tokens) e o menos usado no dia a dia.
106
106
  gatilho: /\b(janela|[áa]rea de trabalho|desktop|abrir? o (app|programa|aplicativo)|clica(r)? (na tela|no [íi]cone)|[íi]cone|mouse|teclado|atalho|print da tela|screenshot|alt\+tab|ctrl\+|bloco de notas|calculadora|explorer|finder)\b/i,
107
107
  },
108
+ {
109
+ /* MCP primeiro, mouse por último. Entra quando o pedido cita um
110
+ serviço — e sempre junto com o navegador e o desktop (gruposDe),
111
+ porque é ali que o agente cairia no clique. */
112
+ nome: 'mcp',
113
+ tools: ['mcp_procurar', 'mcp_conectar', 'mcp_usar'],
114
+ gatilho: /\b(mcp|conector|integra[çc][ãa]o|github|gitlab|notion|slack|discord|drive|google (docs|sheets|agenda|calendar)|figma|planilhas?|sheets|banco( de dados)?|postgres|supabase|firebase|sqlite|jira|linear|trello|asana|e-?mail|gmail|outlook|calend[áa]rio|calendar|agenda|stripe|sentry|vercel|cloudflare|hubspot|salesforce|airtable|whatsapp)\b/i,
115
+ },
116
+ {
117
+ /* OUTRA PASTA (o mudar_pasta). Entra quando o pedido fala de pasta,
118
+ repo ou caminho — e quando o CLI anexa a nota "[outra pasta]", que
119
+ ele põe ao achar o nome de um projeto conhecido no pedido. */
120
+ nome: 'pastas',
121
+ tools: ['mudar_pasta'],
122
+ gatilho: /(\bpastas?\b|\bdiret[óo]rios?\b|\breposit[óo]rios?\b|\brepos?\b|\boutro projeto\b|\bno projeto\b|~\/|\/Users\/|\/home\/|[A-Za-z]:\\)/i,
123
+ },
108
124
  {
109
125
  nome: 'pesquisa',
110
126
  tools: ['pesquisar_referencia'],
@@ -131,10 +147,14 @@ const GRUPOS = [
131
147
  gatilho: /\b(mp4|webm|baix[ae]r?|download|exporta\w*|salv[ae]r? (o )?v[íi]deo|arquivo (do|de) v[íi]deo|me manda o v[íi]deo|manda o arquivo|renderiza\w*)\b/i,
132
148
  },
133
149
  {
150
+ /* Delegar só compensa em tarefa grande; em pedido curto é peso morto.
151
+ Mas o gatilho antigo (vários, refator, migração…) quase nunca
152
+ batia, e o modelo grátis e o local nunca viam o spawn_agent: só o
153
+ modo Claude punha subagente para trabalhar. Agora entra pelo
154
+ ASSUNTO (o que costuma ter partes) e pelo TAMANHO (ver `ehGrande`). */
134
155
  nome: 'delegar',
135
156
  tools: ['spawn_agent'],
136
- // Delegar só compensa em tarefa grande; em pedido curto é peso morto.
137
- gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator|migra[çc][ãa]o|auditor)\b/i,
157
+ gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator\w*|migra[çc][ãa]o|auditor\w*|e tamb[ée]m|e depois|al[ée]m disso|sistema|plataforma|complet[oa]|backend|frontend|front e back|full.?stack|testes|telas|m[óo]dulos|camadas|arquivos|crud|api e|app|aplicativo|site)\b/i,
138
158
  },
139
159
  {
140
160
  nome: 'terminal',
@@ -166,9 +186,35 @@ function gruposDe(texto, forcar = []) {
166
186
  if (g.sempre) { escolhidos.add(g.nome); continue; }
167
187
  if (g.gatilho && g.gatilho.test(t)) escolhidos.add(g.nome);
168
188
  }
189
+ // Pedido grande delega mesmo sem a palavra certa. E quem pode construir
190
+ // um app inteiro também pode dividir: as duas saídas vão juntas, e a
191
+ // seção de delegar diz quando é cada uma.
192
+ if (ehGrande(t) || escolhidos.has('pipeline')) escolhidos.add('delegar');
193
+ // Antes de clicar, o MCP: quem vai mexer no navegador ou no desktop leva os conectores.
194
+ if (escolhidos.has('navegador') || escolhidos.has('computador')) escolhidos.add('mcp');
169
195
  return escolhidos;
170
196
  }
171
197
 
198
+ /* Verbos de AÇÃO: três num pedido são três partes de trabalho. */
199
+ const VERBOS_DE_ACAO = /\b(cri[ae]r?|fa[çc]a|fazer|faz|monta[r]?|monte|constr[óo]i|construa|construir|adicion[ae]r?|implement[ae]r?|escrev[ae]r?|testa[r]?|teste|configur[ae]r?|conect[ae]r?|integr[ae]r?|document[ae]r?|publi(ca|que|car)|deploy|corrij[ae]|corrig[ei]r?|ajust[ae]r?|ger[ae]r?|instal[ae]r?|valid[ae]r?)\b/gi;
200
+
201
+ /**
202
+ * O pedido é GRANDE? Longo, com três verbos de ação ou com lista de três
203
+ * itens. Puro, sem modelo: errar para cima custa ~400 tokens de schema e
204
+ * seção; errar para baixo custa a tarefa inteira feita em fila por um agente só.
205
+ */
206
+ function ehGrande(texto) {
207
+ const t = String(texto || '');
208
+ if (t.length > 280) return true;
209
+ if ((t.match(VERBOS_DE_ACAO) || []).length >= 3) return true;
210
+ // Lista em linhas (- item, * item, 1. item)…
211
+ if ((t.match(/^\s*([-*•]|\d+[.)])\s+\S/gm) || []).length >= 3) return true;
212
+ // …ou corrida, num pedido de fazer: "crie X com API, banco e testes".
213
+ // Sem o verbo, "oi, tudo bem e você?" seria uma lista.
214
+ const corrida = /[^\s,.;:!?]+\s*,\s*[^,.;:!?\n]+?\s+e\s+[^\s,.;:!?]+/.test(t);
215
+ return corrida && (t.match(VERBOS_DE_ACAO) || []).length >= 1;
216
+ }
217
+
172
218
  /** As ferramentas dos grupos escolhidos, na ordem original da lista. */
173
219
  function escolherFerramentas(todas, texto, opts = {}) {
174
220
  const grupos = gruposDe(texto, opts.forcar);
@@ -217,9 +263,20 @@ function jaAprendeu(messages) {
217
263
  * `secoes` é { nome: texto }. O núcleo entra sempre; as outras entram quando o
218
264
  * grupo correspondente entrou.
219
265
  */
266
+ /* Pedido que PILOTA o navegador (abrir, clicar, entrar, preencher). "Crie um
267
+ site" e "sistema de login" também acordam o grupo do navegador, mas ali o
268
+ navegador é o produto, não a tarefa. */
269
+ const PILOTAR_NAVEGADOR = /\b(navegador|browser|chrome|clica\w*|clique|formul[áa]rio|https?:|www\.|url|abr[ae]\w* (o |a )?(site|p[áa]gina)|entr[ae]\w* n[oa] (site|p[áa]gina)|fa[zç]\w* (o )?login|preench\w*)/i;
270
+
220
271
  function montarPrompt(nucleo, secoes, texto, opts = {}) {
221
272
  const grupos = gruposDe(texto, opts.forcar);
222
- const dispensaveis = opts.jaAprendeu || new Set();
273
+ const dispensaveis = new Set(opts.jaAprendeu || []);
274
+ // Quem vai DIVIDIR o trabalho não pilota o navegador: a seção dele (~880
275
+ // tokens) dá lugar à de delegar, e o pedido grande cabe no minuto do
276
+ // plano grátis. As ferramentas ficam; só o manual sai.
277
+ if (grupos.has('delegar') && !grupos.has('computador') && !PILOTAR_NAVEGADOR.test(String(texto || ''))) {
278
+ dispensaveis.add('navegador');
279
+ }
223
280
  const partes = [nucleo];
224
281
  for (const [nome, corpo] of Object.entries(secoes || {})) {
225
282
  if (grupos.has(nome) && corpo && !dispensaveis.has(nome)) partes.push(corpo);
@@ -339,4 +396,4 @@ function caberNoTeto(messages, sobra) {
339
396
  return { messages: atual, cortou: true, como: 'descartou as rodadas mais antigas' };
340
397
  }
341
398
 
342
- module.exports = { GRUPOS, CHARS_POR_TOKEN, REFERENCIAS, LIMITE_REFERENCIA, tokensDe, enxugarSchema, referenciasProtegidas, jaAprendeu, gruposDe, escolherFerramentas, montarPrompt, relatorio, caberNoTeto };
399
+ module.exports = { GRUPOS, CHARS_POR_TOKEN, REFERENCIAS, LIMITE_REFERENCIA, tokensDe, enxugarSchema, referenciasProtegidas, jaAprendeu, gruposDe, ehGrande, escolherFerramentas, montarPrompt, relatorio, caberNoTeto };