primocode 8.42.1 → 9.0.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.
- package/README.md +41 -37
- package/bin/primocode.js +599 -327
- package/lib/acervo.js +8 -1
- package/lib/act.js +205 -114
- package/lib/api.js +35 -4
- package/lib/boas-vindas.js +31 -192
- package/lib/catalogo.js +60 -60
- package/lib/config.js +1 -1
- package/lib/conta.js +3 -24
- package/lib/diff.js +161 -0
- package/lib/diretor.js +37 -47
- package/lib/entrada.js +545 -0
- package/lib/especialistas.js +2 -2
- package/lib/estudio/assets.js +343 -0
- package/lib/estudio/identidade.js +560 -0
- package/lib/estudio/index.js +565 -0
- package/lib/estudio/plano.js +327 -0
- package/lib/estudio/projeto.js +246 -0
- package/lib/estudio/remotion.js +606 -0
- package/lib/estudio/slides.js +190 -0
- package/lib/estudio/video.js +296 -0
- package/lib/fala.js +13 -1
- package/lib/marca.js +4 -1
- package/lib/marketplace.js +189 -0
- package/lib/permissao.js +137 -0
- package/lib/registro.js +229 -0
- package/lib/skills.js +329 -0
- package/lib/tools.js +51 -181
- package/lib/ui.js +7 -3
- package/lib/voz.js +239 -0
- package/package.json +12 -3
- package/skills/acessibilidade/SKILL.md +28 -0
- package/skills/api-que-nao-quebra/SKILL.md +27 -0
- package/skills/depurar-de-verdade/SKILL.md +29 -0
- package/skills/editar-meu-video/SKILL.md +59 -0
- package/skills/escrever-em-portugues/SKILL.md +28 -0
- package/skills/git-limpo/SKILL.md +28 -0
- package/skills/motion-de-primeira/SKILL.md +62 -0
- package/skills/pagina-que-converte/SKILL.md +28 -0
- package/skills/performance-web/SKILL.md +28 -0
- package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
- package/skills/slides-que-prendem/SKILL.md +40 -0
- package/studio/edge.py +16 -66
- package/studio/narrar.py +274 -0
- package/studio/pessoa.py +167 -0
- package/voz/primo_voz/__init__.py +0 -0
- package/voz/primo_voz/__main__.py +325 -0
- package/voz/primo_voz/escuta.py +235 -0
- package/voz/primo_voz/orbe.py +216 -0
- package/lib/audio-livre.js +0 -177
- package/lib/chaves.js +0 -149
- package/lib/claude-engine.js +0 -631
- package/lib/codex-engine.js +0 -208
- package/lib/remotion.js +0 -663
- package/lib/studio.js +0 -960
- package/studio/PRIMOCODE.md +0 -58
- package/studio/README.md +0 -154
- package/studio/__pycache__/edge.cpython-311.pyc +0 -0
- package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
- package/studio/__pycache__/voz.cpython-311.pyc +0 -0
- package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
- package/studio/ferramentas/00-protocolo.md +0 -125
- package/studio/ferramentas/01-modelos.md +0 -270
- package/studio/ferramentas/README.md +0 -32
- package/studio/ferramentas/corte.md +0 -919
- package/studio/ferramentas/grade.md +0 -92
- package/studio/ferramentas/prisma.md +0 -107
- package/studio/ferramentas/prosa.md +0 -86
- package/studio/ferramentas/tela.md +0 -93
- package/studio/ferramentas/traco.md +0 -79
- package/studio/server.py +0 -2254
- package/studio/studio.log +0 -57
- package/studio/voz.py +0 -388
- package/studio/web/analise.html +0 -52
- package/studio/web/core.css +0 -1167
- package/studio/web/fontes/bebas.woff2 +0 -0
- package/studio/web/fontes/grotesk-700.woff2 +0 -0
- package/studio/web/fontes/inter-400.woff2 +0 -0
- package/studio/web/fontes/inter-700.woff2 +0 -0
- package/studio/web/fontes/jetbrains-400.woff2 +0 -0
- package/studio/web/fontes/playfair-700.woff2 +0 -0
- package/studio/web/fontes.css +0 -23
- package/studio/web/home.html +0 -217
- package/studio/web/js/analise.js +0 -510
- package/studio/web/js/audio.js +0 -607
- package/studio/web/js/editor.js +0 -1802
- package/studio/web/js/exportar.js +0 -222
- package/studio/web/js/ferramentas.js +0 -108
- package/studio/web/js/icones.js +0 -120
- package/studio/web/js/midia.js +0 -157
- package/studio/web/js/narracao.js +0 -325
- package/studio/web/js/player.js +0 -903
- package/studio/web/js/render-grade.js +0 -243
- package/studio/web/js/render-prosa.js +0 -104
- package/studio/web/js/render.js +0 -1852
- package/studio/web/js/sfx.js +0 -233
- package/studio/web/js/video.js +0 -464
- package/studio/web/js/voz-neural.js +0 -152
- package/studio/web/render.css +0 -1352
- package/studio/web/tf.html +0 -15
- package/studio/web/tool.html +0 -78
- package/studio/web/view.html +0 -27
package/lib/acervo.js
CHANGED
|
@@ -358,7 +358,14 @@ async function tirarFundo(arquivo, { destino, cor = '0x00FF00', tolerancia = 0.3
|
|
|
358
358
|
});
|
|
359
359
|
if (!metodo) {
|
|
360
360
|
return { ok: false, error: 'nada nesta máquina sabe recortar fundo',
|
|
361
|
-
|
|
361
|
+
// Os dois caminhos, porque servem a casos diferentes: o rembg
|
|
362
|
+
// recorta qualquer foto (é modelo de segmentação), o ffmpeg só
|
|
363
|
+
// resolve fundo de cor chapada — mas o ffmpeg já é preciso para
|
|
364
|
+
// o resto do estúdio, então quem tiver um vídeo rodando aqui
|
|
365
|
+
// provavelmente já o tem.
|
|
366
|
+
comoResolver: 'para recortar qualquer imagem: pip install rembg (ou pipx install rembg). '
|
|
367
|
+
+ 'Para fundo de cor chapada basta o ffmpeg, que o estúdio já usa: '
|
|
368
|
+
+ 'brew install ffmpeg / sudo apt install ffmpeg.' };
|
|
362
369
|
}
|
|
363
370
|
|
|
364
371
|
const rodar = (cmd, args) => new Promise((ok) => {
|
package/lib/act.js
CHANGED
|
@@ -18,6 +18,10 @@ const api = require('./api');
|
|
|
18
18
|
const cfg = require('./config');
|
|
19
19
|
const catalogo = require('./catalogo');
|
|
20
20
|
const repeticao = require('./repeticao');
|
|
21
|
+
const diff = require('./diff');
|
|
22
|
+
const registro = require('./registro');
|
|
23
|
+
const permissao = require('./permissao');
|
|
24
|
+
const skills = require('./skills');
|
|
21
25
|
|
|
22
26
|
// 40, não 15: com o freio de repetição (repeticao.js) e o de rodada parada,
|
|
23
27
|
// volta a mais não é volta em círculo — é trabalho. Quinze acabavam no meio
|
|
@@ -103,6 +107,29 @@ function ident(profundidade) {
|
|
|
103
107
|
return ' '.repeat(profundidade);
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
/* O bloco de diff embaixo do resultado.
|
|
111
|
+
*
|
|
112
|
+
* O `_antes`/`_depois` vem das tools de arquivo e existe só para isto: são as
|
|
113
|
+
* duas versões do texto, que o `limparParaOModelo` apaga antes de a conversa
|
|
114
|
+
* seguir. Sem essa limpeza, cada edição mandaria o arquivo inteiro de volta
|
|
115
|
+
* ao modelo — que acabou de escrevê-lo e não aprende nada relendo. */
|
|
116
|
+
function mostrarDiff(result, profundidade) {
|
|
117
|
+
if (!result || typeof result._antes !== 'string' || typeof result._depois !== 'string') return;
|
|
118
|
+
const bloco = diff.desenhar(result._antes, result._depois, {
|
|
119
|
+
prefixo: fala.recuo(profundidade),
|
|
120
|
+
});
|
|
121
|
+
if (bloco) console.log(bloco);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* O que NÃO pode ir para a conversa. Chave com `_` é desenho de tela: o
|
|
125
|
+
arquivo inteiro, duas vezes, por edição. */
|
|
126
|
+
function limparParaOModelo(result) {
|
|
127
|
+
if (!result || typeof result !== 'object') return result;
|
|
128
|
+
const limpo = {};
|
|
129
|
+
for (const [k, v] of Object.entries(result)) if (!k.startsWith('_')) limpo[k] = v;
|
|
130
|
+
return limpo;
|
|
131
|
+
}
|
|
132
|
+
|
|
106
133
|
function logResult(kind, result, profundidade) {
|
|
107
134
|
const p = fala.resultado(profundidade);
|
|
108
135
|
if (!result) return;
|
|
@@ -118,11 +145,16 @@ function logResult(kind, result, profundidade) {
|
|
|
118
145
|
switch (kind) {
|
|
119
146
|
case 'write_file':
|
|
120
147
|
console.log(p + c.muted(`${result.created ? 'criado' : 'atualizado'} · ${tools.fmtBytes(result.bytes)}`));
|
|
148
|
+
// Reescrever arquivo que já existia apaga o que estava lá. O
|
|
149
|
+
// número de bytes não conta isso; o bloco embaixo conta.
|
|
150
|
+
mostrarDiff(result, profundidade);
|
|
121
151
|
return;
|
|
122
|
-
case 'edit_file':
|
|
152
|
+
case 'edit_file':
|
|
153
|
+
case 'multi_edit': {
|
|
123
154
|
const delta = result.linhasDepois - result.linhasAntes;
|
|
124
155
|
const sinal = delta > 0 ? c.ok(`+${delta}`) : delta < 0 ? c.err(`${delta}`) : c.muted('±0');
|
|
125
156
|
console.log(p + c.muted(`${result.substituicoes} troca${result.substituicoes > 1 ? 's' : ''} · `) + sinal + c.muted(' linhas'));
|
|
157
|
+
mostrarDiff(result, profundidade);
|
|
126
158
|
return;
|
|
127
159
|
}
|
|
128
160
|
case 'read_file':
|
|
@@ -188,20 +220,6 @@ function logResult(kind, result, profundidade) {
|
|
|
188
220
|
}
|
|
189
221
|
return;
|
|
190
222
|
}
|
|
191
|
-
case 'buscar_audio': {
|
|
192
|
-
console.log(p + c.muted(`${result.tipo === 'efeito' ? 'efeito' : 'música'}: `) + c.white(`${result.titulo}`)
|
|
193
|
-
+ c.muted(` — ${result.autor || 'autor desconhecido'}${result.duracao ? ` · ${result.duracao}s` : ''} · ${result.licenca}`));
|
|
194
|
-
console.log(p + c.dim(`${result.url}`));
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
case 'marca_da_empresa': {
|
|
198
|
-
const cores = (result.cores && result.cores.paleta || []).slice(0, 4).join(' ');
|
|
199
|
-
console.log(p + c.white(result.nome || '') + c.muted(` · ${result.site || ''}`));
|
|
200
|
-
console.log(p + c.dim(`cor ${(result.cores && result.cores.principal) || '—'}${cores ? ` · paleta ${cores}` : ''}`
|
|
201
|
-
+ `${(result.fontes || []).length ? ` · fonte ${result.fontes[0]}` : ''}`
|
|
202
|
-
+ `${result.logo ? ` · logo ${result.logo.formato} (${result.logo.fonte})` : ' · sem logo'}`));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
223
|
case 'desktop_windows': {
|
|
206
224
|
console.log(p + c.muted(`${result.total} janela${result.total === 1 ? '' : 's'}`));
|
|
207
225
|
for (const j of (result.janelas || []).slice(0, 5)) console.log(p + c.dim(`${j.app ? j.app + ' · ' : ''}${j.titulo}`.slice(0, 120)));
|
|
@@ -249,7 +267,10 @@ function logResult(kind, result, profundidade) {
|
|
|
249
267
|
// O que volta para o modelo. Cortamos saídas gigantes para não estourar a
|
|
250
268
|
// janela de contexto na iteração seguinte.
|
|
251
269
|
function summarizeForModel(result) {
|
|
252
|
-
|
|
270
|
+
// As chaves com `_` são desenho de tela (as duas versões do arquivo, para
|
|
271
|
+
// o diff). Se escapassem daqui, cada edição mandaria o arquivo inteiro,
|
|
272
|
+
// duas vezes, de volta a um modelo que acabou de escrevê-lo.
|
|
273
|
+
const json = JSON.stringify(limparParaOModelo(result));
|
|
253
274
|
return json.length > 6000 ? json.slice(0, 6000) + '…(truncado)' : json;
|
|
254
275
|
}
|
|
255
276
|
|
|
@@ -369,23 +390,22 @@ async function pausar(ms, subagent) {
|
|
|
369
390
|
*
|
|
370
391
|
* 1. Há um plano (write_todos) com item por fazer — ele mesmo disse que
|
|
371
392
|
* não acabou.
|
|
372
|
-
* 2. O texto é INTENÇÃO:
|
|
373
|
-
*
|
|
393
|
+
* 2. O texto é INTENÇÃO: anuncia o próximo passo ("vou", "em seguida",
|
|
394
|
+
* "próximo passo"), ou termina em dois-pontos e reticências.
|
|
374
395
|
* 3. É curto. Uma resposta de verdade a "explique este erro" tem corpo; um
|
|
375
396
|
* "vou abrir o arquivo agora" não tem.
|
|
376
397
|
*
|
|
377
398
|
* Pergunta ao usuário NÃO conta como parada: o prompt proíbe perguntar, mas
|
|
378
399
|
* se o modelo perguntou, empurrá-lo faria ele decidir sozinho o que talvez
|
|
379
|
-
* não devesse
|
|
380
|
-
* parada. Só intenção declarada e plano aberto.
|
|
400
|
+
* não devesse.
|
|
381
401
|
*/
|
|
382
|
-
const INTENCAO = /(^|[.!\n]\s*)(agora\s+)?(vou|irei|deixa eu|deixe-me|preciso (agora )?(de )?(criar|ler|abrir|rodar|ver|conferir|pesquisar|mandar|fazer|gerar|escrever
|
|
402
|
+
const INTENCAO = /(^|[.!\n]\s*)(agora\s+)?(vou|irei|deixa eu|deixe-me|preciso (agora )?(de )?(criar|ler|abrir|rodar|ver|conferir|pesquisar|mandar|fazer|gerar|escrever)|primeiro (vou|eu)|em seguida|pr[óo]ximo passo|a seguir,?\s+(vou|eu)|vamos (criar|ler|abrir|rodar|ver|conferir|come[çc]ar|fazer|gerar|escrever))\b/i;
|
|
383
403
|
function pareceParadaNoMeio(texto, { todosPendentes = 0, rodouTool = false } = {}) {
|
|
384
404
|
const t = String(texto || '').trim();
|
|
385
405
|
if (!t) return false;
|
|
386
406
|
if (todosPendentes > 0) return true;
|
|
387
|
-
if (t.length > 700) return false;
|
|
388
|
-
if (/\?\s*$/.test(t)) return false;
|
|
407
|
+
if (t.length > 700) return false; // resposta com corpo
|
|
408
|
+
if (/\?\s*$/.test(t)) return false; // perguntou: não é parada
|
|
389
409
|
if (INTENCAO.test(t)) return true;
|
|
390
410
|
if (rodouTool && /[:…]\s*$|\.\.\.\s*$/.test(t)) return true; // "Criando as cenas:" e nada depois
|
|
391
411
|
return false;
|
|
@@ -422,6 +442,17 @@ async function runAct(userPrompt, opts) {
|
|
|
422
442
|
const {
|
|
423
443
|
server, model, token, history, projectDir, projectName,
|
|
424
444
|
ensureWorkDir, profundidade = 0, subagent = false, effort,
|
|
445
|
+
/* Quem responde ao pedido de controle do computador, e o estado da
|
|
446
|
+
resposta. Vêm de fora porque quem sabe perguntar é o terminal, e
|
|
447
|
+
quem sabe lembrar é a SESSÃO — um subagente não pode começar do
|
|
448
|
+
zero e pedir de novo o que já foi autorizado. */
|
|
449
|
+
autorizar = null, controle = null,
|
|
450
|
+
/* O cliente HTTP, injetável. NÃO é mais um comutador de motor — o
|
|
451
|
+
PrimoCode tem um motor só, o do servidor, e não há provedor para o
|
|
452
|
+
usuário escolher. Continua um parâmetro porque é por aqui que o
|
|
453
|
+
teste põe um servidor de mentira no lugar da rede: sem ele,
|
|
454
|
+
`test/repeticao.js` e `test/continuar.js` teriam de falar com a
|
|
455
|
+
internet para verificar o loop do agente. */
|
|
425
456
|
motor = api,
|
|
426
457
|
} = opts || {};
|
|
427
458
|
|
|
@@ -436,6 +467,35 @@ async function runAct(userPrompt, opts) {
|
|
|
436
467
|
let tentativasDeEncerrar = 0;
|
|
437
468
|
// Quem já foi chamado, e quantas vezes. Ver lib/repeticao.js.
|
|
438
469
|
const jaChamado = repeticao.novo();
|
|
470
|
+
/* O livro deste pedido: arquivos, comandos, links, testes e falhas. Sai
|
|
471
|
+
no fim como balanço (ver lib/registro.js). O subagente tem o seu, que
|
|
472
|
+
não se mistura com o do principal — mas o que ele fez também precisa
|
|
473
|
+
aparecer, então o de cima incorpora o de baixo no retorno. */
|
|
474
|
+
const livro = registro.novo();
|
|
475
|
+
/* A autorização do computador vale pela SESSÃO, então o estado vem de
|
|
476
|
+
fora. Um subagente que criasse o seu perguntaria de novo o que já foi
|
|
477
|
+
respondido — e uma pergunta repetida é uma pergunta que se responde
|
|
478
|
+
sem ler. Sem ninguém de fora (teste, pipe), nasce um aqui, que começa
|
|
479
|
+
fechado. */
|
|
480
|
+
const portaoControle = controle || permissao.novo();
|
|
481
|
+
|
|
482
|
+
/* AS SKILLS entram na frente do pedido, e só as que casam com ele.
|
|
483
|
+
*
|
|
484
|
+
* No pedido principal, não no subagente: o subagente já recebe o briefing
|
|
485
|
+
* do especialista dele, e empilhar os dois gastaria o teto de tokens
|
|
486
|
+
* duas vezes para dizer coisas parecidas.
|
|
487
|
+
*
|
|
488
|
+
* O que decide quais entram é o `quando:` de cada skill — ver
|
|
489
|
+
* lib/skills.js, que também explica por que mandar todas em todo pedido
|
|
490
|
+
* não é uma opção num plano limitado por token. */
|
|
491
|
+
let pedido = userPrompt;
|
|
492
|
+
if (!subagent) {
|
|
493
|
+
const comSkills = skills.montar(userPrompt);
|
|
494
|
+
pedido = comSkills.texto;
|
|
495
|
+
if (comSkills.skills.length) {
|
|
496
|
+
fala.pensamento(`seguindo: ${comSkills.skills.map((s) => s.titulo).join(', ')}`, profundidade);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
439
499
|
let voltasEmFalso = 0;
|
|
440
500
|
// Pesquisas que voltaram de mãos vazias. A partir da terceira, a tool
|
|
441
501
|
// para de rodar e devolve ORDEM DE SEGUIR: sem isto o agente entrava em
|
|
@@ -444,10 +504,10 @@ async function runAct(userPrompt, opts) {
|
|
|
444
504
|
// assim que "crie um vídeo para a tostudy" morreu sem entregar nada.
|
|
445
505
|
let pesquisasSecas = 0;
|
|
446
506
|
let rodadasParadas = 0;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
507
|
+
/* Para o `pareceParadaNoMeio`: se alguma tool já rodou nesta tarefa, e o
|
|
508
|
+
último plano que o modelo escreveu (write_todos) — desta TAREFA, não da
|
|
509
|
+
sessão: um plano velho, de um pedido interrompido, não pode empurrar o
|
|
510
|
+
pedido seguinte. */
|
|
451
511
|
let rodouTool = false;
|
|
452
512
|
let todosDaTarefa = [];
|
|
453
513
|
let empurroes = 0;
|
|
@@ -460,32 +520,38 @@ async function runAct(userPrompt, opts) {
|
|
|
460
520
|
if (m && m.role && m.content) messages.push({ role: m.role, content: m.content });
|
|
461
521
|
}
|
|
462
522
|
}
|
|
523
|
+
/* O que VAI para o modelo é `pedido` (com as skills na frente); o que
|
|
524
|
+
fica guardado na memória e no histórico é `userPrompt`, o que a pessoa
|
|
525
|
+
escreveu. Guardar o texto inflado faria a conversa crescer com o mesmo
|
|
526
|
+
conhecimento repetido em cada turno. */
|
|
463
527
|
/* ── O GATILHO DO DIRETOR ────────────────────────────────────────────
|
|
464
528
|
*
|
|
465
529
|
* "A IA identifica que precisa criar vídeo, ativa o agente, esse agente
|
|
466
530
|
* faz a estrutura e manda pra IA, e depois ela só faz o vídeo."
|
|
467
531
|
*
|
|
468
532
|
* Deixar isso só escrito no catálogo não basta: com um pedido curto
|
|
469
|
-
* ("faz um vídeo do produto") o modelo pula direto para
|
|
470
|
-
* porque
|
|
471
|
-
*
|
|
472
|
-
*
|
|
533
|
+
* ("faz um vídeo do produto") o modelo pula direto para montar cena,
|
|
534
|
+
* porque montar cena é o que ele sabe fazer — e o roteiro, escrito no
|
|
535
|
+
* meio do JSON, vira legenda de slide. Quem reconhece o pedido é uma
|
|
536
|
+
* regra (diretor.precisaDeProducao), e ela entra como ORDEM na conversa,
|
|
537
|
+
* não como conselho perdido no meio de quarenta e sete ferramentas.
|
|
473
538
|
*
|
|
474
539
|
* Num subagente não entra: ele já está fazendo uma parte de um plano, e
|
|
475
540
|
* um diretor dentro do diretor é uma volta a mais para o mesmo lugar. */
|
|
476
541
|
if (!subagent && profundidade === 0) {
|
|
477
|
-
const producao = require('./diretor.js').precisaDeProducao(
|
|
542
|
+
const producao = require('./diretor.js').precisaDeProducao(pedido);
|
|
478
543
|
if (producao.producao) {
|
|
479
544
|
messages.push({ role: 'system', content:
|
|
480
545
|
'ESTE PEDIDO É DE PRODUÇÃO AUDIOVISUAL. Antes de criar qualquer peça, chame '
|
|
481
546
|
+ `planejar_producao com o pedido do usuário (formato "${producao.formato}"). `
|
|
482
|
-
+ 'Ele devolve a pauta —
|
|
483
|
-
+ 'e é ela que você produz, cena a cena. Não comece pelo
|
|
484
|
-
+ 'escrito no meio do JSON da cena vira legenda de slide,
|
|
547
|
+
+ 'Ele devolve a pauta — arco, narração falada, voz, direção de arte e a mídia a buscar — '
|
|
548
|
+
+ 'e é ela que você produz, cena a cena, compondo com as primitivas. Não comece pelo '
|
|
549
|
+
+ 'estudio_plano: o roteiro escrito no meio do JSON da cena vira legenda de slide, '
|
|
550
|
+
+ 'que é exatamente o que se quer evitar.' });
|
|
485
551
|
}
|
|
486
552
|
}
|
|
487
553
|
|
|
488
|
-
messages.push({ role: 'user', content:
|
|
554
|
+
messages.push({ role: 'user', content: pedido });
|
|
489
555
|
|
|
490
556
|
let finalText = '';
|
|
491
557
|
|
|
@@ -664,10 +730,10 @@ async function runAct(userPrompt, opts) {
|
|
|
664
730
|
iteration--;
|
|
665
731
|
continue;
|
|
666
732
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
733
|
+
/* PAROU NO MEIO? Texto de intenção ("agora vou criar…") ou plano
|
|
734
|
+
com item aberto não é entrega. Em vez de encerrar, a tarefa
|
|
735
|
+
volta: "isso foi uma promessa — faça". Três empurrões; se
|
|
736
|
+
depois disso ele insistir em só falar, a resposta é dele. */
|
|
671
737
|
const pendentes = todosDaTarefa.filter((t) => t.status !== 'completed').length;
|
|
672
738
|
if (finalText && empurroes < MAX_EMPURROES && !PARAR
|
|
673
739
|
&& pareceParadaNoMeio(finalText, { todosPendentes: pendentes, rodouTool })) {
|
|
@@ -767,37 +833,34 @@ async function runAct(userPrompt, opts) {
|
|
|
767
833
|
continue;
|
|
768
834
|
}
|
|
769
835
|
|
|
770
|
-
/* O
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
aquele módulo a conhecer o loop do agente — e um segundo lugar
|
|
774
|
-
que sabe abrir conversa com o modelo é um segundo lugar para
|
|
775
|
-
esquecer de passar o token, a pasta ou o motor. */
|
|
776
|
-
if (tc.name === 'construir_app') {
|
|
836
|
+
/* O DIRETOR mora aqui pela mesma razão: ele É uma conversa
|
|
837
|
+
nova, e quem sabe abrir conversa é este arquivo. */
|
|
838
|
+
if (tc.name === 'planejar_producao') {
|
|
777
839
|
algoRodou = true;
|
|
778
840
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
779
|
-
const r = await
|
|
841
|
+
const r = await rodarDiretor(tc.arguments || {}, {
|
|
780
842
|
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
|
|
781
843
|
});
|
|
782
844
|
logResult(tc.name, r, profundidade);
|
|
783
845
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
784
|
-
if (r && r.ok && catalogo.escreveArquivo(tc.name)) temPasta = true;
|
|
785
846
|
continue;
|
|
786
847
|
}
|
|
787
848
|
|
|
788
|
-
/* O
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
849
|
+
/* O PIPELINE DE QUATRO PAPÉIS mora aqui pelo mesmo motivo que o
|
|
850
|
+
`spawn_agent`: ele PRECISA rodar agentes, e quem sabe rodar
|
|
851
|
+
agente é este arquivo. Pôr isto no `tools.execute` obrigaria
|
|
852
|
+
aquele módulo a conhecer o loop do agente — e um segundo lugar
|
|
853
|
+
que sabe abrir conversa com o modelo é um segundo lugar para
|
|
854
|
+
esquecer de passar o token ou a pasta. */
|
|
855
|
+
if (tc.name === 'construir_app') {
|
|
794
856
|
algoRodou = true;
|
|
795
857
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
796
|
-
const r = await
|
|
797
|
-
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
|
|
858
|
+
const r = await rodarPipeline(tc.arguments || {}, {
|
|
859
|
+
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, livro, autorizar, controle: portaoControle,
|
|
798
860
|
});
|
|
799
861
|
logResult(tc.name, r, profundidade);
|
|
800
862
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
863
|
+
if (r && r.ok && catalogo.escreveArquivo(tc.name)) temPasta = true;
|
|
801
864
|
continue;
|
|
802
865
|
}
|
|
803
866
|
|
|
@@ -834,12 +897,30 @@ async function runAct(userPrompt, opts) {
|
|
|
834
897
|
continue;
|
|
835
898
|
}
|
|
836
899
|
|
|
900
|
+
/* O PORTÃO do controle do computador. Antes da ação, não depois:
|
|
901
|
+
depois é o ponteiro já tendo andado. Ler a tela não passa por
|
|
902
|
+
aqui — ver é o que ele precisa fazer para saber onde clicar. */
|
|
903
|
+
if (permissao.precisa(tc.name)) {
|
|
904
|
+
const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
|
|
905
|
+
if (!porta.ok) {
|
|
906
|
+
fala.acao(tc.name, tc.arguments, profundidade);
|
|
907
|
+
console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
|
|
908
|
+
registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
|
|
909
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: porta.error }) });
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
837
914
|
algoRodou = true;
|
|
838
915
|
rodouTool = true;
|
|
839
916
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
840
917
|
const result = await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual });
|
|
841
918
|
logResult(tc.name, result, profundidade);
|
|
842
919
|
if (tc.name === 'write_todos' && result && Array.isArray(result.todos)) todosDaTarefa = result.todos;
|
|
920
|
+
// Além de narrar, ANOTA: no fim do pedido sai o balanço do que
|
|
921
|
+
// nasceu, mudou, rodou e falhou. Narrar sozinho não basta — numa
|
|
922
|
+
// tarefa de trinta ações, a primeira já saiu da tela.
|
|
923
|
+
registro.anotar(livro, tc.name, tc.arguments, result);
|
|
843
924
|
|
|
844
925
|
// Uma escrita bem-sucedida limpa a dívida — mas só a do MESMO
|
|
845
926
|
// arquivo. Escrever algo sem relação (ou um run_shell qualquer)
|
|
@@ -950,6 +1031,7 @@ async function runAct(userPrompt, opts) {
|
|
|
950
1031
|
return {
|
|
951
1032
|
userMessage: { role: 'user', content: userPrompt },
|
|
952
1033
|
assistantMessage: { role: 'assistant', content: finalText },
|
|
1034
|
+
registro: livro,
|
|
953
1035
|
// Esc Esc no meio de um plano de vários passos precisa parar o plano
|
|
954
1036
|
// INTEIRO, não só o passo — senão o próximo passo começa logo depois
|
|
955
1037
|
// de o usuário ter mandado parar.
|
|
@@ -978,8 +1060,16 @@ async function runSubagent(args, ctx) {
|
|
|
978
1060
|
profundidade: profundidade + 1,
|
|
979
1061
|
subagent: true,
|
|
980
1062
|
motor,
|
|
1063
|
+
autorizar: ctx.autorizar,
|
|
1064
|
+
controle: ctx.controle, // o mesmo objeto: a resposta vale para todos
|
|
981
1065
|
});
|
|
982
1066
|
|
|
1067
|
+
/* O que o subagente fez entra no livro de quem o chamou. Sem isto, o
|
|
1068
|
+
balanço no fim mentiria por omissão: o especialista escreveu cinco
|
|
1069
|
+
arquivos e a tela diria "nenhum arquivo". A conversa dele continua
|
|
1070
|
+
separada — o que se junta é o registro do que aconteceu no disco. */
|
|
1071
|
+
if (ctx.livro && r.registro) registro.juntar(ctx.livro, r.registro);
|
|
1072
|
+
|
|
983
1073
|
const resumo = r.assistantMessage.content || 'sem resultado';
|
|
984
1074
|
fala.agenteVoltou(resumo, profundidade);
|
|
985
1075
|
return resumo;
|
|
@@ -1000,6 +1090,62 @@ async function runSubagent(args, ctx) {
|
|
|
1000
1090
|
* coisa: quem acabou de escrever o código é o pior revisor dele, porque lê o
|
|
1001
1091
|
* que quis escrever em vez do que escreveu.
|
|
1002
1092
|
*/
|
|
1093
|
+
/**
|
|
1094
|
+
* O DIRETOR: estrutura antes de alguém produzir.
|
|
1095
|
+
*
|
|
1096
|
+
* Por que uma conversa PRÓPRIA, e não mais um parágrafo no prompt de quem
|
|
1097
|
+
* produz: o modelo que está montando as cenas já gastou a atenção dele em
|
|
1098
|
+
* JSON — fonte, cor, duração, posição — e é ali que o roteiro morre. Vira
|
|
1099
|
+
* legenda do que aparece na tela ("Vantagens", "Nossos serviços"), sem arco,
|
|
1100
|
+
* sem narração escrita, sem decisão de voz. O diretor não vê JSON de cena
|
|
1101
|
+
* nenhum: ele decide o que se conta, em que ordem, em quantos segundos, com
|
|
1102
|
+
* que voz e com que direção de arte. Depois disso o produtor tem pouco a
|
|
1103
|
+
* inventar — e inventar é onde ele erra.
|
|
1104
|
+
*/
|
|
1105
|
+
async function rodarDiretor(args, ctx) {
|
|
1106
|
+
const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
|
|
1107
|
+
if (profundidade > 0) {
|
|
1108
|
+
return { ok: false, error: 'Recusado: o diretor não roda dentro de um subagente. '
|
|
1109
|
+
+ 'Produza com o plano que você já recebeu.' };
|
|
1110
|
+
}
|
|
1111
|
+
const diretor = require('./diretor.js');
|
|
1112
|
+
const correr = async (prompt, rotulo) => {
|
|
1113
|
+
if (PARAR) return '';
|
|
1114
|
+
fala.agente('Diretor', rotulo, profundidade);
|
|
1115
|
+
const r = await runAct(prompt, {
|
|
1116
|
+
server, model, token, projectDir, projectName,
|
|
1117
|
+
history: [], profundidade: profundidade + 1, subagent: true, motor,
|
|
1118
|
+
});
|
|
1119
|
+
return (r && r.assistantMessage && r.assistantMessage.content) || '';
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
const r = await diretor.planejar({
|
|
1123
|
+
pedido: args.pedido || args.prompt || '',
|
|
1124
|
+
segundos: Number(args.segundos) || 30,
|
|
1125
|
+
formato: args.formato === 'apresentacao' ? 'apresentacao' : 'video',
|
|
1126
|
+
contexto: args.contexto || '',
|
|
1127
|
+
correr,
|
|
1128
|
+
mostrar: (t) => fala.agente('Diretor', t, profundidade),
|
|
1129
|
+
});
|
|
1130
|
+
if (!r.ok) return r;
|
|
1131
|
+
|
|
1132
|
+
/* A PAUTA é o que volta para o produtor, não o JSON cru. O JSON cru
|
|
1133
|
+
convida a copiar campo por campo para dentro do plano de cena — e aí o
|
|
1134
|
+
"fundo": "verde descendo" viraria uma string num lugar que espera cor.
|
|
1135
|
+
A pauta é prosa: o produtor LÊ e compõe com os primitivos que ele tem. */
|
|
1136
|
+
return {
|
|
1137
|
+
ok: true,
|
|
1138
|
+
aprovado: r.aprovado,
|
|
1139
|
+
defeitos: r.defeitos.length ? r.defeitos : undefined,
|
|
1140
|
+
pauta: diretor.pauta(r.plano),
|
|
1141
|
+
voz: r.plano.voz,
|
|
1142
|
+
midia: r.plano.midia,
|
|
1143
|
+
trilha: r.plano.trilha,
|
|
1144
|
+
segundos: r.plano.segundos,
|
|
1145
|
+
proporcao: r.plano.proporcao,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1003
1149
|
async function rodarPipeline(args, ctx) {
|
|
1004
1150
|
const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
|
|
1005
1151
|
if (profundidade > 0) {
|
|
@@ -1035,60 +1181,5 @@ async function rodarPipeline(args, ctx) {
|
|
|
1035
1181
|
});
|
|
1036
1182
|
}
|
|
1037
1183
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
* "Tenha uma IA específica pra roteiro que monta tudo — narração, qual voz,
|
|
1041
|
-
* roteiro. Ela estrutura tudo antes de mandar pra IA."
|
|
1042
|
-
*
|
|
1043
|
-
* As regras, o prompt e a conferência do plano moram em `diretor.js`. Aqui é
|
|
1044
|
-
* a costura: uma conversa NOVA para o diretor (ele não vê a do usuário nem a
|
|
1045
|
-
* do produtor — é isso que faz o roteiro parar de virar legenda do slide) e a
|
|
1046
|
-
* pauta de volta, que é o que o produtor recebe no lugar do pedido cru.
|
|
1047
|
-
*/
|
|
1048
|
-
async function rodarDiretor(args, ctx) {
|
|
1049
|
-
const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
|
|
1050
|
-
const diretor = require('./diretor.js');
|
|
1051
|
-
|
|
1052
|
-
const correr = async (prompt, rotulo) => {
|
|
1053
|
-
if (PARAR) return '';
|
|
1054
|
-
fala.agente('Diretor', rotulo, profundidade);
|
|
1055
|
-
const r = await runAct(prompt, {
|
|
1056
|
-
server, model, token, projectDir, projectName,
|
|
1057
|
-
history: [], profundidade: profundidade + 1, subagent: true, motor,
|
|
1058
|
-
});
|
|
1059
|
-
return (r && r.assistantMessage && r.assistantMessage.content) || '';
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
const r = await diretor.planejar({
|
|
1063
|
-
pedido: args.pedido || args.prompt || '',
|
|
1064
|
-
segundos: Number(args.segundos) || 30,
|
|
1065
|
-
formato: args.formato === 'apresentacao' ? 'apresentacao' : 'video',
|
|
1066
|
-
contexto: args.contexto || '',
|
|
1067
|
-
correr,
|
|
1068
|
-
mostrar: (linha) => console.log(fala.resultado(profundidade) + c.muted(linha)),
|
|
1069
|
-
});
|
|
1070
|
-
if (!r.ok) return r;
|
|
1071
|
-
|
|
1072
|
-
return {
|
|
1073
|
-
ok: true,
|
|
1074
|
-
motor: r.plano.motor,
|
|
1075
|
-
voz: r.plano.voz,
|
|
1076
|
-
midia: r.plano.midia,
|
|
1077
|
-
trilha: r.plano.trilha,
|
|
1078
|
-
pauta: diretor.pauta(r.plano),
|
|
1079
|
-
plano: r.plano,
|
|
1080
|
-
ressalvas: r.defeitos,
|
|
1081
|
-
aviso: 'A ESTRUTURA ESTÁ PRONTA. Agora só PRODUZA: busque a mídia listada com '
|
|
1082
|
-
+ 'midia_procurar + midia_baixar (use o "src" que volta em fundo.imagem e em src); '
|
|
1083
|
-
+ 'a TRILHA vem do buscar_audio (já baixa e credita) e a identidade de uma empresa, do marca_da_empresa. '
|
|
1084
|
-
+ `e monte com studio_criar (${r.plano.formato === 'apresentacao' ? 'prisma' : 'corte'}) `
|
|
1085
|
-
+ 'seguindo a pauta cena a cena. Não invente cena, não troque a ordem, não reescreva a narração. '
|
|
1086
|
-
+ (r.plano.motor.motor === 'remotion'
|
|
1087
|
-
? 'Depois de criar, gere o projeto Remotion com remotion_projeto: '
|
|
1088
|
-
+ r.plano.motor.motivo + '.'
|
|
1089
|
-
: 'O motor é o estúdio: ' + r.plano.motor.motivo + '.'),
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
module.exports = { runAct, interromper, compactar, motivoDaParada, ehTetoDeToken, ehTransitorio, esperaDoErro,
|
|
1094
|
-
anotacaoDoQueJaFoi, encolherParaContinuar, pareceParadaNoMeio, MAX_ITERATIONS, MAX_EMPURROES };
|
|
1184
|
+
module.exports = { runAct, interromper, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
|
|
1185
|
+
anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
|
package/lib/api.js
CHANGED
|
@@ -220,6 +220,14 @@ function requestResumo(serverUrl, payload) {
|
|
|
220
220
|
return request(serverUrl, '/api/resumir', body, token);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/* A transcrição do áudio, com o tempo de cada trecho — é dela que sai a
|
|
224
|
+
legenda sincronizada. Quem tem a chave do provedor é o servidor, não o
|
|
225
|
+
CLI: essa é a promessa do produto. */
|
|
226
|
+
function requestTranscricao(serverUrl, payload) {
|
|
227
|
+
const { token, ...body } = payload;
|
|
228
|
+
return request(serverUrl, '/api/transcrever', body, token);
|
|
229
|
+
}
|
|
230
|
+
|
|
223
231
|
// Refino do pedido: manda o texto cru, recebe a ordem de serviço.
|
|
224
232
|
function requestRefino(serverUrl, payload) {
|
|
225
233
|
const { token, ...body } = payload;
|
|
@@ -232,20 +240,43 @@ function requestPlano(serverUrl, payload) {
|
|
|
232
240
|
return request(serverUrl, '/api/plano', body, token);
|
|
233
241
|
}
|
|
234
242
|
|
|
243
|
+
/* Os conectores do servidor. Rota própria em vez de espremer do /health: o
|
|
244
|
+
/health carrega a corrente inteira dos três apelidos, e quem quer só saber
|
|
245
|
+
quem está de pé não precisa baixar isso. */
|
|
246
|
+
function getConectores(serverUrl, token) {
|
|
247
|
+
return pedirGet(serverUrl, '/api/conectores', token);
|
|
248
|
+
}
|
|
249
|
+
|
|
235
250
|
function getHealth(serverUrl, token) {
|
|
251
|
+
return pedirGet(serverUrl, '/health', token);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function pedirGet(serverUrl, rota, token) {
|
|
236
255
|
return new Promise((resolve, reject) => {
|
|
237
|
-
const url = new URL(
|
|
256
|
+
const url = new URL(rota, serverUrl);
|
|
238
257
|
const client = url.protocol === 'https:' ? https : http;
|
|
239
258
|
const req = client.get(buildOptions(url, 'GET', {}, null, token), (res) => {
|
|
240
259
|
let body = '';
|
|
241
260
|
res.on('data', (chunk) => { body += chunk; });
|
|
242
261
|
res.on('end', () => {
|
|
243
262
|
try { resolve(JSON.parse(body)); }
|
|
244
|
-
catch { reject(new Error(`Resposta inválida
|
|
263
|
+
catch { reject(new Error(`Resposta inválida de ${rota} (HTTP ${res.statusCode})`)); }
|
|
245
264
|
});
|
|
246
265
|
});
|
|
247
|
-
|
|
266
|
+
/* Sem prazo, um servidor mudo pendura o comando para sempre — e
|
|
267
|
+
pendura calado, que é o pior jeito de travar.
|
|
268
|
+
|
|
269
|
+
Relógio de PAREDE, nunca `req.setTimeout`: aquele mede silêncio do
|
|
270
|
+
socket, e o silêncio de 40 segundos da máquina subindo no Render é
|
|
271
|
+
justamente o que não pode ser cortado. Aqui a conta é a resposta
|
|
272
|
+
inteira, que é o que de fato tem de acabar. */
|
|
273
|
+
const relogio = setTimeout(() => {
|
|
274
|
+
req.destroy(erroDeTempo('respondeu', PRAZO_RESPOSTA));
|
|
275
|
+
}, PRAZO_RESPOSTA);
|
|
276
|
+
if (relogio.unref) relogio.unref();
|
|
277
|
+
req.on('close', () => clearTimeout(relogio));
|
|
278
|
+
req.on('error', (e) => { clearTimeout(relogio); reject(e); });
|
|
248
279
|
});
|
|
249
280
|
}
|
|
250
281
|
|
|
251
|
-
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, getHealth };
|
|
282
|
+
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, requestTranscricao, getHealth, getConectores };
|