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