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