primocode 10.5.0 → 10.6.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 +2 -2
- package/bin/primocode.js +117 -24
- package/lib/act.js +141 -9
- package/lib/agents-md.js +140 -0
- package/lib/auditoria.js +122 -0
- package/lib/browser.js +48 -1
- package/lib/catalogo.js +24 -4
- package/lib/comandos-extra.js +2 -0
- package/lib/config.js +32 -4
- package/lib/entrada.js +33 -11
- package/lib/estudio/colecao.js +34 -1
- package/lib/estudio/diretor.js +1 -0
- package/lib/estudio/falantes.js +30 -6
- package/lib/estudio/hibrido.js +144 -8
- package/lib/estudio/index.js +151 -59
- package/lib/estudio/kit/mundo.jsx +1060 -0
- package/lib/estudio/kit/voxel.jsx +315 -70
- package/lib/estudio/kit.js +1 -1
- package/lib/estudio/kokoro.js +18 -4
- package/lib/estudio/margem.js +1 -1
- package/lib/estudio/mundo.js +305 -0
- package/lib/estudio/personagem.js +164 -11
- package/lib/estudio/producao.js +107 -17
- package/lib/estudio/projeto.js +77 -22
- package/lib/leitura.js +176 -0
- package/lib/local/cerebro.json +180 -3
- package/lib/local/entrada.js +61 -4
- package/lib/mcp.js +296 -12
- package/lib/memoria-conversa.js +32 -3
- package/lib/mencoes.js +5 -1
- package/lib/nome-curto.js +126 -0
- package/lib/nome-projeto.js +5 -2
- package/lib/outra-pasta.js +161 -0
- package/lib/perigo.js +34 -2
- package/lib/referencia.js +40 -11
- package/lib/repeticao.js +20 -1
- package/lib/tela.js +1 -1
- package/lib/tools.js +76 -6
- package/lib/travado.js +141 -0
- package/lib/ui.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.
|
|
1
|
+
# PrimoCode v10.6.0
|
|
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.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.6.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/bin/primocode.js
CHANGED
|
@@ -21,11 +21,14 @@ const api = require('../lib/api');
|
|
|
21
21
|
const tools = require('../lib/tools');
|
|
22
22
|
const cfg = require('../lib/config');
|
|
23
23
|
const nomeProjeto = require('../lib/nome-projeto');
|
|
24
|
+
const { nomeCurto } = require('../lib/nome-curto');
|
|
24
25
|
const provedorLocal = require('../lib/local/provedor');
|
|
25
26
|
const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro, mandarDuranteATarefa, sobrasDaCaixa } = require('../lib/act');
|
|
26
27
|
const fala = require('../lib/fala');
|
|
27
28
|
const boasVindas = require('../lib/boas-vindas');
|
|
28
29
|
const pasta = require('../lib/pasta');
|
|
30
|
+
const outraPasta = require('../lib/outra-pasta');
|
|
31
|
+
const agentsMd = require('../lib/agents-md');
|
|
29
32
|
const catalogo = require('../lib/catalogo');
|
|
30
33
|
const effort = require('../lib/effort');
|
|
31
34
|
const memoria = require('../lib/memoria-conversa');
|
|
@@ -793,32 +796,82 @@ function projectDir() {
|
|
|
793
796
|
// ── Envio ──────────────────────────────────────────────────────────────
|
|
794
797
|
|
|
795
798
|
/**
|
|
796
|
-
* Falou de
|
|
799
|
+
* Falou de OUTRA pasta? Ele LÊ lá sem pedir, e só TROCA com o seu sim.
|
|
797
800
|
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
801
|
+
* "Quando eu perguntar de algo de outra pasta, ela vai procurar lá; e se ela
|
|
802
|
+
* está aberta numa pasta e eu trabalho em outra, ela muda — com permissão."
|
|
803
|
+
* A pasta citada (caminho, "pasta X", ou o nome de um projeto/pasta recente)
|
|
804
|
+
* fica liberada para leitura (lib/tools.js). A troca pergunta, Enter é NÃO.
|
|
805
|
+
*
|
|
806
|
+
* Devolve a nota para o modelo quando a pessoa ficou onde estava — é ela que
|
|
807
|
+
* diz ao agente que pode procurar lá. Com mais de uma pasta do mesmo nome,
|
|
808
|
+
* lista e espera o /dir: heurística não escolhe onde o agente escreve.
|
|
800
809
|
*/
|
|
801
|
-
function talvezTrocarDePasta(userText) {
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
console.log(c.
|
|
810
|
-
achado.ambiguo.forEach((a) => console.log(` ${c.dim('·')} ${c.white(shortPath(a.caminho))}`));
|
|
810
|
+
async function talvezTrocarDePasta(userText) {
|
|
811
|
+
const r = outraPasta.detectar(userText, {
|
|
812
|
+
conhecidas: outraPasta.conhecidas({ raiz: cfg.ROOT_DIR, recentes: cfg.pastasRecentes() }),
|
|
813
|
+
atual: projectDir(),
|
|
814
|
+
resolver: (cand) => pasta.resolverPasta(cand, { raiz: cfg.ROOT_DIR, cwd: CWD_INICIAL }),
|
|
815
|
+
});
|
|
816
|
+
if (r.ambiguo) {
|
|
817
|
+
console.log(c.warn(` Achei ${r.ambiguo.length} pastas chamadas "${r.candidato}":`));
|
|
818
|
+
r.ambiguo.forEach((a) => console.log(` ${c.dim('·')} ${c.white(shortPath(a.caminho))}`));
|
|
811
819
|
console.log(c.dim(' Escolha com /dir <caminho>.'));
|
|
812
|
-
return;
|
|
820
|
+
return '';
|
|
813
821
|
}
|
|
814
|
-
|
|
815
|
-
if (!
|
|
822
|
+
const alvo = (r.pastas || []).find((p) => pastaSegura(p.caminho));
|
|
823
|
+
if (!alvo) return ''; // home ou raiz: só por /dir explícito
|
|
824
|
+
|
|
825
|
+
tools.liberarLeitura(alvo.caminho);
|
|
826
|
+
|
|
827
|
+
/* No pedido único (`primocode "crie x na pasta loja"`) não há a quem
|
|
828
|
+
perguntar — e a pasta veio escrita no próprio comando, com marca
|
|
829
|
+
("pasta", caminho). Aí vale o que ele sempre fez: troca. Nome solto
|
|
830
|
+
("olha o landing-padaria") nunca troca sem pergunta. */
|
|
831
|
+
if (state.semTerminal) {
|
|
832
|
+
if (alvo.como !== 'caminho') return outraPasta.notaDeConsulta(alvo.caminho, projectDir());
|
|
833
|
+
entrarNoProjeto(alvo.caminho, 'detectada');
|
|
834
|
+
return '';
|
|
835
|
+
}
|
|
836
|
+
console.log(c.dim(` ⎿ você citou ${shortPath(alvo.caminho)} — posso ler lá daqui mesmo`));
|
|
837
|
+
if (!(await perguntarTroca(alvo.caminho))) return outraPasta.notaDeConsulta(alvo.caminho, projectDir());
|
|
838
|
+
trocarPara(alvo.caminho, 'detectada', `você falou "${alvo.nome}"`);
|
|
839
|
+
return '';
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* A pergunta da troca. Enter sozinho é NÃO: trocar muda onde os arquivos
|
|
843
|
+
nascem, e o padrão de quem apertou Enter sem ler não pode ser esse. */
|
|
844
|
+
async function perguntarTroca(caminho, motivo = '') {
|
|
845
|
+
if (motivo) console.log(' ' + c.muted(motivo));
|
|
846
|
+
const dito = String(await ask(`${c.brand('?')} Trabalhar em ${c.white(shortPath(caminho))} a partir de agora? ${c.muted('(s/N)')} `))
|
|
847
|
+
.trim().toLowerCase().replace(/[.!?,;:…]+/g, ' ').trim();
|
|
848
|
+
if (!process.stdin.isTTY) process.stdout.write('\n');
|
|
849
|
+
return /^(s|sim|y|yes|pode|ok|claro|muda|troca)\b/.test(dito);
|
|
850
|
+
}
|
|
816
851
|
|
|
817
|
-
|
|
818
|
-
|
|
852
|
+
/* Entra na pasta e avisa. A memória dela (AGENTS.md) entra no próximo
|
|
853
|
+
pedido pelo buildMemoryContext, porque ainda não está no resumo. */
|
|
854
|
+
function trocarPara(caminho, origem, porque) {
|
|
855
|
+
entrarNoProjeto(caminho, origem);
|
|
856
|
+
const tem = agentsMd.ler(caminho) ? ' · com memória das sessões anteriores' : '';
|
|
857
|
+
console.log(c.ok(` Trabalhando em ${shortPath(caminho)}`) + c.dim(` (${porque}${tem})`));
|
|
819
858
|
refreshPrompt();
|
|
820
859
|
}
|
|
821
860
|
|
|
861
|
+
/* O mudar_pasta do modelo chega aqui (lib/outra-pasta.js → pedirTroca). */
|
|
862
|
+
async function mudarPastaPeloAgente({ caminho, motivo }) {
|
|
863
|
+
if (state.semTerminal) return { ok: false, error: 'sem terminal para o usuário responder.' };
|
|
864
|
+
console.log('');
|
|
865
|
+
console.log(' ' + c.warn('▸▸') + ' ' + c.bold(c.white('O Primo quer trabalhar em outra pasta.')));
|
|
866
|
+
if (!(await perguntarTroca(caminho, motivo ? `Motivo: ${motivo}` : ''))) {
|
|
867
|
+
console.log(' ' + c.muted('Fica onde está.') + c.dim(' Ele ainda pode ler a outra pasta.'));
|
|
868
|
+
tools.liberarLeitura(caminho);
|
|
869
|
+
return { ok: false };
|
|
870
|
+
}
|
|
871
|
+
trocarPara(caminho, 'detectada', 'você autorizou');
|
|
872
|
+
return { ok: true, caminho, memoria: agentsMd.ler(caminho) || undefined };
|
|
873
|
+
}
|
|
874
|
+
|
|
822
875
|
/**
|
|
823
876
|
* Decide, de uma vez, onde este pedido vai trabalhar.
|
|
824
877
|
*
|
|
@@ -827,10 +880,10 @@ function talvezTrocarDePasta(userText) {
|
|
|
827
880
|
* `primocode "crie x na pasta loja"` ignorava a pasta e criava projeto novo.
|
|
828
881
|
* Duas cópias da mesma decisão significam que uma delas está errada.
|
|
829
882
|
*/
|
|
830
|
-
function prepararPasta(userText) {
|
|
883
|
+
async function prepararPasta(userText) {
|
|
831
884
|
// A pasta é decidida ANTES de o agente começar. Trocar no meio faria as
|
|
832
885
|
// primeiras tools agirem num lugar e as seguintes em outro.
|
|
833
|
-
talvezTrocarDePasta(userText);
|
|
886
|
+
const notaOutraPasta = await talvezTrocarDePasta(userText);
|
|
834
887
|
|
|
835
888
|
const plan = planWorkDir(userText);
|
|
836
889
|
batizarDepois(plan, userText);
|
|
@@ -843,6 +896,7 @@ function prepararPasta(userText) {
|
|
|
843
896
|
console.log(c.ok(` Continuando em ${state.currentProject}`) + c.muted(` (${shortPath(plan.dir)})`));
|
|
844
897
|
refreshPrompt();
|
|
845
898
|
}
|
|
899
|
+
plan.notaOutraPasta = notaOutraPasta;
|
|
846
900
|
return plan;
|
|
847
901
|
}
|
|
848
902
|
|
|
@@ -1133,7 +1187,7 @@ async function enviarPedido(userText) {
|
|
|
1133
1187
|
// mídia inventada, limite estourado) para chegar no mesmo lugar.
|
|
1134
1188
|
if (await baixarCriacaoDireto(userText)) { refreshPrompt(); return; }
|
|
1135
1189
|
|
|
1136
|
-
const plan = prepararPasta(userText);
|
|
1190
|
+
const plan = await prepararPasta(userText);
|
|
1137
1191
|
|
|
1138
1192
|
// O Chrome só aparece na tela quando o pedido é de navegador (lib/browser.js, definirVisivel).
|
|
1139
1193
|
require('../lib/browser.js').definirVisivel(querNavegador(userText));
|
|
@@ -1213,15 +1267,22 @@ async function enviarPedido(userText) {
|
|
|
1213
1267
|
// No pedido único (`primo "…"`) não há ninguém para responder:
|
|
1214
1268
|
// sem `autorizar`, o que pede licença é recusado (lib/perigo.js).
|
|
1215
1269
|
autorizar: state.semTerminal ? undefined : autorizarControle,
|
|
1270
|
+
// A memória do AGENTS.md só vai se o resumo ainda não a carrega.
|
|
1271
|
+
memoriaDaPasta: !memoria.jaDobrou(state.memoria, plan.dir),
|
|
1272
|
+
mudarPasta: state.semTerminal ? null : mudarPastaPeloAgente,
|
|
1216
1273
|
controle: state.controle,
|
|
1217
1274
|
permissoes: state.config.permissoes,
|
|
1218
1275
|
// Pedido de mexer no computador leva a dica que libera as
|
|
1219
1276
|
// ferramentas desktop_* no servidor (ver DICA_COMPUTADOR).
|
|
1220
1277
|
instrucao: [i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
|
|
1221
|
-
i === 0 ? require('../lib/estudio/referencias.js').instrucao() : ''
|
|
1278
|
+
i === 0 ? require('../lib/estudio/referencias.js').instrucao() : '',
|
|
1279
|
+
i === 0 ? plan.notaOutraPasta || '' : ''].filter(Boolean).join('\n\n'),
|
|
1222
1280
|
});
|
|
1223
1281
|
|
|
1224
1282
|
r = await rodarPasso();
|
|
1283
|
+
// O mudar_pasta trocou no meio: os passos seguintes seguem lá.
|
|
1284
|
+
if (r && r.pasta && path.resolve(r.pasta) !== path.resolve(plan.dir)) { plan.dir = r.pasta; plan.pending = false; }
|
|
1285
|
+
if (r && r.estudio) state.estudioAtivo = r.estudio;
|
|
1225
1286
|
/* Passo que parou no meio NÃO devolve a tarefa para a pessoa
|
|
1226
1287
|
dividir. Se ele já tinha feito alguma coisa (arquivo, comando),
|
|
1227
1288
|
o próprio CLI pede a continuação — com a lista do que já existe
|
|
@@ -1423,6 +1484,14 @@ async function comprimirConversa(forcado = false, { silencioso = false } = {}) {
|
|
|
1423
1484
|
const sp = silencioso ? { stop() {} } : new Spinner('resumindo a conversa').start();
|
|
1424
1485
|
try {
|
|
1425
1486
|
const conversa = state.history;
|
|
1487
|
+
const dir = projectDir();
|
|
1488
|
+
/* A memória que o AGENTS.md desta pasta já tinha entra no resumo
|
|
1489
|
+
(uma vez por pasta): sem isto a primeira resposta de uma sessão nova
|
|
1490
|
+
reescreveria o AGENTS.md só com ela, e o "de onde parei" de ontem
|
|
1491
|
+
sumia. */
|
|
1492
|
+
if (!memoria.jaDobrou(state.memoria, dir) && !(state.memoria.semente && state.memoria.semente.pasta === dir)) {
|
|
1493
|
+
state.memoria = memoria.semear(state.memoria, dir, agentsMd.ler(dir));
|
|
1494
|
+
}
|
|
1426
1495
|
const nova = await memoria.atualizar(state.memoria, conversa, ({ resumo, mensagens }) =>
|
|
1427
1496
|
api.requestResumo(state.config.server, { resumo, mensagens, token: state.config.token })
|
|
1428
1497
|
.then((r) => r.resumo));
|
|
@@ -1431,6 +1500,7 @@ async function comprimirConversa(forcado = false, { silencioso = false } = {}) {
|
|
|
1431
1500
|
if (state.history !== conversa) { sp.stop(); return; }
|
|
1432
1501
|
state.memoria = nova;
|
|
1433
1502
|
sp.stop();
|
|
1503
|
+
gravarMemoriaDaPasta(dir);
|
|
1434
1504
|
const e = memoria.economia(state.memoria, state.history);
|
|
1435
1505
|
if (!silencioso && e.poupado > 0) console.log(c.dim(` ⎿ conversa resumida · ${e.cheio} → ${e.enxuto} tokens por pedido`));
|
|
1436
1506
|
} catch {
|
|
@@ -1451,6 +1521,24 @@ function resumirDepois() {
|
|
|
1451
1521
|
atual.then(() => { if (state.resumindo === atual) state.resumindo = null; });
|
|
1452
1522
|
}
|
|
1453
1523
|
|
|
1524
|
+
/* ── O AGENTS.md DE CADA PASTA ───────────────────────────────────────────
|
|
1525
|
+
* "Eu preciso de um AGENTS.md com a memória da conversa em cada pasta."
|
|
1526
|
+
* O resumo da conversa vai para o AGENTS.md da pasta de trabalho (só o bloco
|
|
1527
|
+
* do PrimoCode; o resto do arquivo é da pessoa — lib/agents-md.js), e para a
|
|
1528
|
+
* pasta do projeto do Estúdio em que se mexeu, se houver. A raiz
|
|
1529
|
+
* ~/primocode fica de fora: é o corredor, não um projeto. */
|
|
1530
|
+
function gravarMemoriaDaPasta(dir = projectDir()) {
|
|
1531
|
+
const resumo = state.memoria && state.memoria.resumo;
|
|
1532
|
+
if (!resumo) return;
|
|
1533
|
+
try { agentsMd.gravar(dir, resumo, { raiz: cfg.ROOT_DIR }); } catch { /* memória é conveniência */ }
|
|
1534
|
+
if (state.estudioAtivo) {
|
|
1535
|
+
try {
|
|
1536
|
+
const p = require('../lib/estudio/projeto.js').ler(state.estudioAtivo);
|
|
1537
|
+
if (p) agentsMd.gravar(p.pasta, resumo, { titulo: p.titulo });
|
|
1538
|
+
} catch { /* projeto sumiu */ }
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1454
1542
|
async function esperarResumo(tetoMs = 6000) {
|
|
1455
1543
|
if (!state.resumindo) return;
|
|
1456
1544
|
await Promise.race([state.resumindo, new Promise((ok) => setTimeout(ok, tetoMs))]);
|
|
@@ -1495,7 +1583,11 @@ function contextoDoProjeto() {
|
|
|
1495
1583
|
instruções do projeto entram. Sem isto o PRIMO.md valeria para o agente e
|
|
1496
1584
|
seria ignorado quando o modo auto manda o pedido para a conversa. */
|
|
1497
1585
|
function comInstrucoes(history) {
|
|
1498
|
-
const
|
|
1586
|
+
const dir = state.currentProject || state.workDir ? projectDir() : null;
|
|
1587
|
+
let texto = mencoesLib.instrucoesDoProjeto(dir);
|
|
1588
|
+
// A memória do AGENTS.md também vale no chat, enquanto o resumo não a tem.
|
|
1589
|
+
const daPasta = dir && !memoria.jaDobrou(state.memoria, dir) ? agentsMd.ler(dir) : '';
|
|
1590
|
+
if (daPasta) texto = [texto, `[AGENTS.md — memória desta pasta: de onde a conversa parou]\n${daPasta}`].filter(Boolean).join('\n\n');
|
|
1499
1591
|
return texto ? [{ role: 'system', content: texto }, ...history] : history;
|
|
1500
1592
|
}
|
|
1501
1593
|
|
|
@@ -2004,7 +2096,8 @@ async function handleSlash(text) {
|
|
|
2004
2096
|
sp.stop();
|
|
2005
2097
|
const nomeNovo = extra.nomeDoNovo({
|
|
2006
2098
|
descricao: rest, nomes, sugerido, derivar: deriveProjectName,
|
|
2007
|
-
|
|
2099
|
+
// A régua curta (lib/nome-curto.js) também no /novo.
|
|
2100
|
+
slugify: (t) => nomeCurto(t) || cfg.slugify(t), semColisao: nomeProjeto.semColisao,
|
|
2008
2101
|
});
|
|
2009
2102
|
const dir = cfg.createProjectDir(nomeNovo);
|
|
2010
2103
|
entrarNoProjeto(dir, 'projeto');
|
package/lib/act.js
CHANGED
|
@@ -26,6 +26,8 @@ const skills = require('./skills');
|
|
|
26
26
|
const checkpoint = require('./checkpoint');
|
|
27
27
|
const tempos = require('./tempos');
|
|
28
28
|
const preLeitura = require('./pre-leitura');
|
|
29
|
+
const travado = require('./travado');
|
|
30
|
+
const auditoria = require('./auditoria');
|
|
29
31
|
|
|
30
32
|
// 40, não 15: com o freio de repetição (repeticao.js) e o de rodada parada,
|
|
31
33
|
// volta a mais não é volta em círculo — é trabalho. Quinze acabavam no meio
|
|
@@ -41,7 +43,7 @@ const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
|
|
|
41
43
|
// As leituras que, repetidas sem mudança no meio, devolvem o guardado.
|
|
42
44
|
const RELEITURA = new Set(['read_file', 'list_dir']);
|
|
43
45
|
/* 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']);
|
|
46
|
+
const SO_LEITURA = new Set(['read_file', 'list_dir', 'glob', 'grep', 'pesquisar_referencia', 'estudio_site', 'mcp_procurar']);
|
|
45
47
|
const ehLeitura = (tc) => SO_LEITURA.has(tc.name)
|
|
46
48
|
|| (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) || '')));
|
|
47
49
|
|
|
@@ -275,6 +277,17 @@ function logResult(kind, result, profundidade) {
|
|
|
275
277
|
}
|
|
276
278
|
return;
|
|
277
279
|
}
|
|
280
|
+
case 'mcp_procurar': {
|
|
281
|
+
console.log(p + c.muted(`${(result.resultados || []).length} MCP para "${result.busca}"`));
|
|
282
|
+
for (const r of (result.resultados || []).slice(0, 4)) console.log(p + c.dim(`${r.nome} · ${r.descricao}`.slice(0, 110)));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
case 'mcp_conectar':
|
|
286
|
+
console.log(p + c.ok('conectado') + c.muted(` · ${result.servidor} · ${(result.ferramentas || []).length} tools`));
|
|
287
|
+
return;
|
|
288
|
+
case 'mcp_usar':
|
|
289
|
+
console.log(p + c.muted(`${result.servidor}.${result.ferramenta} · ${String(result.resultado || '').length} caracteres`));
|
|
290
|
+
return;
|
|
278
291
|
case 'desktop_windows': {
|
|
279
292
|
console.log(p + c.muted(`${result.total} janela${result.total === 1 ? '' : 's'}`));
|
|
280
293
|
for (const j of (result.janelas || []).slice(0, 5)) console.log(p + c.dim(`${j.app ? j.app + ' · ' : ''}${j.titulo}`.slice(0, 120)));
|
|
@@ -645,6 +658,11 @@ function historicoEnxuto(history) {
|
|
|
645
658
|
* vêm do que o opencode e o Codex põem nos prompts deles para o modelo não
|
|
646
659
|
* reler o que fez; entram em TODO pedido, curtas, porque o plano conta token.
|
|
647
660
|
* A conferência de verdade é nossa (a página aberta no Chrome escondido). */
|
|
661
|
+
/* MCP primeiro, mouse por último: vale no modo local e no subagente, que não
|
|
662
|
+
recebem o prompt do servidor. */
|
|
663
|
+
const FERRAMENTA_CERTA = '- FERRAMENTA CERTA: para agir num serviço ou app, use primeiro um MCP (veja [mcp]). '
|
|
664
|
+
+ 'Sem MCP: mcp_procurar → mcp_conectar → mcp_usar. Mouse/desktop_* só quando não houver MCP, API ou CLI.';
|
|
665
|
+
|
|
648
666
|
const RITMO = '[ritmo]\n'
|
|
649
667
|
+ '- 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'
|
|
650
668
|
+ '- Várias ações independentes vão na MESMA resposta (ex.: escrever index.html, style.css e script.js juntos).\n'
|
|
@@ -653,24 +671,34 @@ const RITMO = '[ritmo]\n'
|
|
|
653
671
|
+ '- VOCÊ DECIDE O CAMINHO. Pedido simples: faça direto. Pedido grande com partes independentes: write_todos com as partes, '
|
|
654
672
|
+ 'depois spawn_agent — um por parte, até 8 NA MESMA resposta (rodam juntos), cada um com o especialista e tudo que ele precisa saber. '
|
|
655
673
|
+ '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'
|
|
656
|
-
+ '- Antes de dizer pronto, PROVE: rode o teste ou o comando, abra o resultado, olhe a imagem. Sem prova, não está pronto
|
|
674
|
+
+ '- Antes de dizer pronto, PROVE: rode o teste ou o comando, abra o resultado, olhe a imagem. Sem prova, não está pronto.\n'
|
|
675
|
+
+ FERRAMENTA_CERTA;
|
|
657
676
|
|
|
658
677
|
/* O ritmo do subagente: o mesmo, sem a parte de orquestrar (ele não abre outro agente). */
|
|
659
678
|
const RITMO_AGENTE = '[ritmo]\n'
|
|
660
679
|
+ '- O resultado de write_file/edit_file é a verdade. Não releia o que acabou de escrever.\n'
|
|
661
680
|
+ '- 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'
|
|
662
681
|
+ '- Várias ações independentes na MESMA resposta.\n'
|
|
663
|
-
+ '- Faça o que a sua parte pede, prove (teste ou comando) e chame finish. Polimento além do pedido não é sua parte
|
|
682
|
+
+ '- Faça o que a sua parte pede, prove (teste ou comando) e chame finish. Polimento além do pedido não é sua parte.\n'
|
|
683
|
+
+ FERRAMENTA_CERTA;
|
|
664
684
|
|
|
665
685
|
/* `pedido` é opcional: com ele, o arquivo citado no pedido é o primeiro a
|
|
666
686
|
entrar já lido (ver lib/pre-leitura.js). */
|
|
667
|
-
function buildMemoryContext(projectDir, projectName, pedido = '') {
|
|
687
|
+
function buildMemoryContext(projectDir, projectName, pedido = '', { memoriaDaPasta = true } = {}) {
|
|
668
688
|
const parts = [];
|
|
669
689
|
// As instruções vêm primeiro: são regra de quem manda no projeto, e a
|
|
670
690
|
// memória é o que o agente anotou. Entram aqui (e não no bin) para valer
|
|
671
691
|
// igual no terminal, no modo voz e em cada passo de um plano.
|
|
672
692
|
const instrucoes = require('./mencoes.js').instrucoesDoProjeto(projectDir);
|
|
673
693
|
if (instrucoes) parts.push(instrucoes);
|
|
694
|
+
/* A conversa das sessões anteriores NESTA pasta (lib/agents-md.js). É o
|
|
695
|
+
que faz a sessão de amanhã começar de onde a de hoje parou. Quando o
|
|
696
|
+
resumo da conversa já a carrega (`memoriaDaPasta: false`, quem sabe é o
|
|
697
|
+
bin), ela não vem de novo. */
|
|
698
|
+
if (memoriaDaPasta) {
|
|
699
|
+
const daPasta = require('./agents-md.js').ler(projectDir);
|
|
700
|
+
if (daPasta) parts.push(`[AGENTS.md — memória desta pasta: de onde a conversa parou]\n${daPasta}`);
|
|
701
|
+
}
|
|
674
702
|
const geral = cfg.readGeneralMemory();
|
|
675
703
|
if (geral.trim()) parts.push(`[memory-geral.md]\n${geral}`);
|
|
676
704
|
const projeto = cfg.readProjectMemory(projectDir);
|
|
@@ -693,6 +721,14 @@ async function runAct(userPrompt, opts) {
|
|
|
693
721
|
const {
|
|
694
722
|
server, model, token, history, projectDir, projectName,
|
|
695
723
|
ensureWorkDir, profundidade = 0, subagent = false, effort,
|
|
724
|
+
/* A memória do AGENTS.md entra no contexto? O bin diz não quando o
|
|
725
|
+
resumo da conversa já a dobrou (lib/memoria-conversa.js). */
|
|
726
|
+
memoriaDaPasta = true,
|
|
727
|
+
/* Quem troca a pasta de trabalho quando o modelo chama mudar_pasta:
|
|
728
|
+
async ({ caminho, motivo }) → { ok, caminho, memoria?, error? }.
|
|
729
|
+
Vem do bin, porque é lá que se PERGUNTA ao usuário — sem ele (pipe,
|
|
730
|
+
subagente) a troca é recusada. */
|
|
731
|
+
mudarPasta = null,
|
|
696
732
|
/* Quem responde ao pedido de controle do computador, e o estado da
|
|
697
733
|
resposta. Vêm de fora porque quem sabe perguntar é o terminal, e
|
|
698
734
|
quem sabe lembrar é a SESSÃO — um subagente não pode começar do
|
|
@@ -723,6 +759,8 @@ async function runAct(userPrompt, opts) {
|
|
|
723
759
|
teste que troca o medidor não pode subir um Chrome de verdade. */
|
|
724
760
|
aquecerPagina = opts && opts.medirPagina ? null
|
|
725
761
|
: () => require('./browser.js').aquecerRevisor(),
|
|
762
|
+
/* Quem é este agente no livro de ações (auditoria.js). */
|
|
763
|
+
rotulo = subagent ? 'subagente' : 'principal',
|
|
726
764
|
} = opts || {};
|
|
727
765
|
|
|
728
766
|
// A pasta de projeto só nasce quando o agente for mesmo MEXER EM ARQUIVO.
|
|
@@ -731,6 +769,7 @@ async function runAct(userPrompt, opts) {
|
|
|
731
769
|
// nada. Quem decide é o catálogo: `escreve: true`.
|
|
732
770
|
let criarPasta = ensureWorkDir || null;
|
|
733
771
|
let pastaAtual = projectDir; // onde as tools agem AGORA
|
|
772
|
+
let estudioDaTarefa = null; // o projeto do Estúdio desta tarefa, se houver
|
|
734
773
|
let temPasta = !criarPasta; // já é a pasta definitiva?
|
|
735
774
|
/* Cria a pasta do projeto pendente, uma vez. Chamado antes de qualquer
|
|
736
775
|
ferramenta que faz código (ver `criaProjeto` no catálogo) — e ANTES do
|
|
@@ -783,6 +822,17 @@ async function runAct(userPrompt, opts) {
|
|
|
783
822
|
? `${texto}\n\nAtenção: abri a página e ela ainda tem problema — ${avisoDaPagina.trim()}` : texto);
|
|
784
823
|
// Quem já foi chamado, e quantas vezes. Ver lib/repeticao.js.
|
|
785
824
|
const jaChamado = repeticao.novo();
|
|
825
|
+
/* Andando em círculo? (lib/travado.js — o subagente de 01/10 que releu o
|
|
826
|
+
mesmo arquivo por uma hora). Empurra uma vez; na segunda, para. */
|
|
827
|
+
const circulo = travado.novo();
|
|
828
|
+
let paradoPorTravar = null;
|
|
829
|
+
/* Anota ANTES de agir (lib/auditoria.js): recusado também fica no livro. */
|
|
830
|
+
const anotar = (tc, decisao, motivo) => {
|
|
831
|
+
const id = auditoria.antes({ agente: rotulo, ferramenta: tc.name, args: tc.arguments, decisao, motivo });
|
|
832
|
+
const falhou = auditoria.problema();
|
|
833
|
+
if (falhou) console.log(c.warn(`⚠ ${falhou}`));
|
|
834
|
+
return id;
|
|
835
|
+
};
|
|
786
836
|
// O que read_file/list_dir devolveram, pela chave da época (repeticao.js).
|
|
787
837
|
const leiturasGuardadas = new Map();
|
|
788
838
|
// O que ELE escreveu inteiro nesta tarefa, por caminho absoluto — para
|
|
@@ -834,7 +884,7 @@ async function runAct(userPrompt, opts) {
|
|
|
834
884
|
|
|
835
885
|
const messages = [];
|
|
836
886
|
if (!subagent) {
|
|
837
|
-
const memory = buildMemoryContext(projectDir, projectName, userPrompt);
|
|
887
|
+
const memory = buildMemoryContext(projectDir, projectName, userPrompt, { memoriaDaPasta });
|
|
838
888
|
if (memory) messages.push({ role: 'system', content: `Memória persistente:\n\n${memory}` });
|
|
839
889
|
for (const m of historicoEnxuto(history)) messages.push(m);
|
|
840
890
|
} else {
|
|
@@ -1253,11 +1303,12 @@ async function runAct(userPrompt, opts) {
|
|
|
1253
1303
|
if (spawns.length > 1 && !PARAR) {
|
|
1254
1304
|
const juntos = spawns.slice(0, 8);
|
|
1255
1305
|
fala.paralelo(juntos.length, profundidade);
|
|
1306
|
+
const ids = juntos.map((tc) => anotar(tc, 'permitida'));
|
|
1256
1307
|
const rs = await Promise.all(juntos.map((tc) =>
|
|
1257
1308
|
runSubagent(tc.arguments || {}, {
|
|
1258
1309
|
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
|
|
1259
1310
|
}).catch((e) => `Subagente falhou: ${e && e.message}`)));
|
|
1260
|
-
juntos.forEach((tc, i) => resultadosSpawn.set(tc.id, rs[i]));
|
|
1311
|
+
juntos.forEach((tc, i) => { resultadosSpawn.set(tc.id, rs[i]); auditoria.depois(ids[i], { ok: true }); });
|
|
1261
1312
|
}
|
|
1262
1313
|
|
|
1263
1314
|
/* LEITURAS JUNTAS. O RITMO pede ao modelo para juntar as ações
|
|
@@ -1265,10 +1316,23 @@ async function runAct(userPrompt, opts) {
|
|
|
1265
1316
|
grep e uma pesquisa. Só que o laço abaixo rodava uma de cada vez, e
|
|
1266
1317
|
a rodada custava a soma. O que só LÊ (não muda arquivo, tela nem
|
|
1267
1318
|
conta) roda tudo de uma vez aqui; o laço usa o resultado pronto,
|
|
1268
|
-
passando pelos mesmos freios de sempre (repetição, registro).
|
|
1319
|
+
passando pelos mesmos freios de sempre (repetição, registro).
|
|
1320
|
+
Só entra no lote o que o laço NÃO vai recusar: a releitura, a
|
|
1321
|
+
mesma leitura duas vezes na resposta e a pesquisa depois de três
|
|
1322
|
+
secas ficam de fora. Senão rodavam aqui e o livro anotava
|
|
1323
|
+
"recusada" para o que já tinha rodado. */
|
|
1269
1324
|
const adiantadas = new Map();
|
|
1270
|
-
const
|
|
1325
|
+
const chavesDoLote = new Set();
|
|
1326
|
+
const leituras = toolCalls.filter(ehLeitura).filter((tc) => {
|
|
1327
|
+
if (tc.name === 'pesquisar_referencia' && pesquisasSecas >= 3) return false;
|
|
1328
|
+
const { repetida, chave } = repeticao.espiar(jaChamado, tc.name, tc.arguments);
|
|
1329
|
+
if (repetida || chavesDoLote.has(chave)) return false;
|
|
1330
|
+
chavesDoLote.add(chave);
|
|
1331
|
+
return true;
|
|
1332
|
+
});
|
|
1333
|
+
const idsAdiantados = new Map();
|
|
1271
1334
|
if (leituras.length > 1 && !PARAR) {
|
|
1335
|
+
leituras.slice(0, 8).forEach((tc) => idsAdiantados.set(tc.id, anotar(tc, 'permitida')));
|
|
1272
1336
|
const rs = await Promise.all(leituras.slice(0, 8).map((tc) =>
|
|
1273
1337
|
tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api })
|
|
1274
1338
|
.catch((e) => ({ ok: false, error: e && e.message }))));
|
|
@@ -1276,6 +1340,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1276
1340
|
traco(`${adiantadas.size} leituras em paralelo`);
|
|
1277
1341
|
}
|
|
1278
1342
|
|
|
1343
|
+
let motivoTravou = null;
|
|
1279
1344
|
for (const tc of toolCalls) {
|
|
1280
1345
|
if (PARAR) break;
|
|
1281
1346
|
if (tc.name === 'finish') {
|
|
@@ -1309,13 +1374,37 @@ async function runAct(userPrompt, opts) {
|
|
|
1309
1374
|
continue;
|
|
1310
1375
|
}
|
|
1311
1376
|
|
|
1377
|
+
/* TROCAR DE PASTA mora aqui, e não no tools.execute: ela muda
|
|
1378
|
+
`pastaAtual`, que é deste laço, e precisa de quem PERGUNTA ao
|
|
1379
|
+
usuário (o bin). Sem a resposta dele, não troca. */
|
|
1380
|
+
if (tc.name === 'mudar_pasta') {
|
|
1381
|
+
algoRodou = true;
|
|
1382
|
+
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1383
|
+
const idA = anotar(tc, 'permitida');
|
|
1384
|
+
const r = await require('./outra-pasta.js').pedirTroca(tc.arguments || {}, {
|
|
1385
|
+
atual: pastaAtual, subagent, trocar: mudarPasta,
|
|
1386
|
+
});
|
|
1387
|
+
if (r.ok) {
|
|
1388
|
+
pastaAtual = r.caminho;
|
|
1389
|
+
criarPasta = null;
|
|
1390
|
+
temPasta = true;
|
|
1391
|
+
}
|
|
1392
|
+
logResult(tc.name, r, profundidade);
|
|
1393
|
+
auditoria.depois(idA, r);
|
|
1394
|
+
repeticao.executou(jaChamado, tc.name, tc.arguments);
|
|
1395
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1312
1399
|
if (tc.name === 'spawn_agent') {
|
|
1313
1400
|
algoRodou = true;
|
|
1401
|
+
const idA = resultadosSpawn.has(tc.id) ? null : anotar(tc, 'permitida');
|
|
1314
1402
|
const r = resultadosSpawn.has(tc.id)
|
|
1315
1403
|
? resultadosSpawn.get(tc.id)
|
|
1316
1404
|
: await runSubagent(tc.arguments || {}, {
|
|
1317
1405
|
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
|
|
1318
1406
|
});
|
|
1407
|
+
if (idA) auditoria.depois(idA, { ok: true });
|
|
1319
1408
|
repeticao.executou(jaChamado, tc.name, tc.arguments);
|
|
1320
1409
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: r + lembreteDeParalelo(todosDaTarefa, spawns.length) });
|
|
1321
1410
|
continue;
|
|
@@ -1326,10 +1415,12 @@ async function runAct(userPrompt, opts) {
|
|
|
1326
1415
|
if (tc.name === 'planejar_producao') {
|
|
1327
1416
|
algoRodou = true;
|
|
1328
1417
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1418
|
+
const idA = anotar(tc, 'permitida');
|
|
1329
1419
|
const r = await rodarDiretor(tc.arguments || {}, {
|
|
1330
1420
|
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes,
|
|
1331
1421
|
});
|
|
1332
1422
|
logResult(tc.name, r, profundidade);
|
|
1423
|
+
auditoria.depois(idA, r);
|
|
1333
1424
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
1334
1425
|
continue;
|
|
1335
1426
|
}
|
|
@@ -1344,10 +1435,12 @@ async function runAct(userPrompt, opts) {
|
|
|
1344
1435
|
algoRodou = true;
|
|
1345
1436
|
garantirPasta();
|
|
1346
1437
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1438
|
+
const idA = anotar(tc, 'permitida');
|
|
1347
1439
|
const r = await rodarPipeline(tc.arguments || {}, {
|
|
1348
1440
|
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor, permissoes, livro, autorizar, controle: portaoControle,
|
|
1349
1441
|
});
|
|
1350
1442
|
logResult(tc.name, r, profundidade);
|
|
1443
|
+
auditoria.depois(idA, r);
|
|
1351
1444
|
repeticao.executou(jaChamado, tc.name, tc.arguments);
|
|
1352
1445
|
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
1353
1446
|
if (r && r.ok && catalogo.escreveArquivo(tc.name)) temPasta = true;
|
|
@@ -1362,6 +1455,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1362
1455
|
const eco = repeticao.ver(jaChamado, tc.name, tc.arguments);
|
|
1363
1456
|
if (eco.repetida) {
|
|
1364
1457
|
voltasEmFalso++;
|
|
1458
|
+
anotar(tc, 'recusada', `repetida (${eco.vezes}ª vez) — não executou de novo`);
|
|
1365
1459
|
/* RELER SEM MUDANÇA devolve o que já foi lido, uma vez.
|
|
1366
1460
|
O aviso seco ("o resultado está na conversa acima") mentia
|
|
1367
1461
|
depois do `compactar`: a leitura velha já tinha virado 300
|
|
@@ -1377,6 +1471,11 @@ async function runAct(userPrompt, opts) {
|
|
|
1377
1471
|
continue;
|
|
1378
1472
|
}
|
|
1379
1473
|
|
|
1474
|
+
/* Só conta o que passou do freio de repetição: a chamada idêntica
|
|
1475
|
+
já é barrada lá (e a rodada parada desiste sozinha). Aqui entra
|
|
1476
|
+
o que escapa dele — a fatia sobreposta, a volta sem nada novo. */
|
|
1477
|
+
motivoTravou = motivoTravou || travado.observar(circulo, tc.name, tc.arguments, pastaAtual);
|
|
1478
|
+
|
|
1380
1479
|
if (criarPasta && catalogo.criaProjeto(tc.name, tc.arguments)) garantirPasta();
|
|
1381
1480
|
|
|
1382
1481
|
/* RELER O QUE ACABOU DE ESCREVER. O modelo grátis escreve o
|
|
@@ -1413,6 +1512,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1413
1512
|
|
|
1414
1513
|
if (tc.name === 'pesquisar_referencia' && pesquisasSecas >= 3) {
|
|
1415
1514
|
voltasEmFalso++;
|
|
1515
|
+
anotar(tc, 'recusada', 'três pesquisas seguidas sem fonte');
|
|
1416
1516
|
messages.push({
|
|
1417
1517
|
role: 'tool',
|
|
1418
1518
|
tool_call_id: tc.id,
|
|
@@ -1431,6 +1531,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1431
1531
|
if (permissao.precisa(tc.name)) {
|
|
1432
1532
|
const porta = await permissao.pedir(portaoControle, tc.name, tc.arguments, autorizar);
|
|
1433
1533
|
if (!porta.ok) {
|
|
1534
|
+
anotar(tc, 'recusada', porta.error);
|
|
1434
1535
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1435
1536
|
console.log(fala.resultado(profundidade) + c.warn('não autorizado'));
|
|
1436
1537
|
registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: porta.error });
|
|
@@ -1448,6 +1549,7 @@ async function runAct(userPrompt, opts) {
|
|
|
1448
1549
|
projectDir: pastaAtual, subagent, perguntar: autorizar,
|
|
1449
1550
|
});
|
|
1450
1551
|
if (!risco.ok) {
|
|
1552
|
+
anotar(tc, 'recusada', risco.motivo || risco.error);
|
|
1451
1553
|
fala.acao(tc.name, tc.arguments, profundidade);
|
|
1452
1554
|
console.log(fala.resultado(profundidade) + c.warn(`não autorizado — ${risco.motivo}`));
|
|
1453
1555
|
registro.anotar(livro, tc.name, tc.arguments, { ok: false, error: risco.error });
|
|
@@ -1467,9 +1569,15 @@ async function runAct(userPrompt, opts) {
|
|
|
1467
1569
|
estudio_video transcreve no servidor, e sem eles a ferramenta
|
|
1468
1570
|
respondia "a transcrição precisa do servidor". */
|
|
1469
1571
|
traco(`executando ${tc.name}`);
|
|
1572
|
+
const idAcao = idsAdiantados.get(tc.id) || anotar(tc, 'permitida');
|
|
1470
1573
|
const result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
|
|
1471
1574
|
: await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api });
|
|
1575
|
+
auditoria.depois(idAcao, result);
|
|
1472
1576
|
traco(`${tc.name} devolveu ok=${result && result.ok}`);
|
|
1577
|
+
if (/^estudio_|^planejar_producao$/.test(tc.name) && result && result.ok !== false) {
|
|
1578
|
+
const id = result.id || (result.projeto && result.projeto.id) || (tc.arguments && tc.arguments.id);
|
|
1579
|
+
if (typeof id === 'string' && id) estudioDaTarefa = id;
|
|
1580
|
+
}
|
|
1473
1581
|
logResult(tc.name, result, profundidade);
|
|
1474
1582
|
repeticao.executou(jaChamado, tc.name, tc.arguments);
|
|
1475
1583
|
if (RELEITURA.has(tc.name) && result && result.ok !== false) {
|
|
@@ -1548,6 +1656,18 @@ async function runAct(userPrompt, opts) {
|
|
|
1548
1656
|
// A conferência da página tem linha própria: aqui fica só o resto.
|
|
1549
1657
|
tempos.somar('ferramentas', Date.now() - tTools - (msConferindo - conferindoAntes));
|
|
1550
1658
|
|
|
1659
|
+
/* ANDANDO EM CÍRCULO (lib/travado.js): a primeira vez, um empurrão
|
|
1660
|
+
com o que ele está repetindo; a segunda, para — antes da cota. */
|
|
1661
|
+
const giro = travado.fimDaVolta(circulo);
|
|
1662
|
+
const travou = finished ? null : travado.decidir(circulo, motivoTravou || giro);
|
|
1663
|
+
if (travou && travou.acao === 'empurrar') {
|
|
1664
|
+
traco(`travado: ${motivoTravou || giro}`);
|
|
1665
|
+
messages.push({ role: 'user', content: travou.texto });
|
|
1666
|
+
} else if (travou) {
|
|
1667
|
+
paradoPorTravar = travou.texto;
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1551
1671
|
// Uma volta inteira sem executar nada é o modelo preso. Insistir custa
|
|
1552
1672
|
// um pedido ao servidor por volta, e as 15 voltas saíam caras para
|
|
1553
1673
|
// terminar no mesmo lugar — melhor parar e devolver a vez ao usuário.
|
|
@@ -1587,6 +1707,11 @@ async function runAct(userPrompt, opts) {
|
|
|
1587
1707
|
// que "parou sem concluir" não pode virar fato para o passo seguinte.
|
|
1588
1708
|
const concluiu = Boolean(finalText) && !PARAR;
|
|
1589
1709
|
|
|
1710
|
+
if (paradoPorTravar && !finalText) {
|
|
1711
|
+
finalText = paradoPorTravar;
|
|
1712
|
+
if (!subagent) fala.aviso(finalText);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1590
1715
|
if (PARAR && !finalText) {
|
|
1591
1716
|
finalText = 'Parado por você (Esc Esc). O que já foi feito, ficou feito.';
|
|
1592
1717
|
if (!subagent) fala.aviso(finalText);
|
|
@@ -1609,7 +1734,9 @@ async function runAct(userPrompt, opts) {
|
|
|
1609
1734
|
// (buildMemoryContext), e sessão sem projeto nomeado (ex.: primeiro pedido
|
|
1610
1735
|
// antes de /project) tem `projectName` undefined mesmo já tendo mexido em
|
|
1611
1736
|
// arquivo de verdade — a memória era descartada nesse caso, sempre.
|
|
1612
|
-
|
|
1737
|
+
// A raiz de trabalho não é projeto: um memory.md solto em ~/primocode
|
|
1738
|
+
// era o que um pedido de vídeo (que trabalha na raiz) deixava para trás.
|
|
1739
|
+
if (!subagent && temPasta && require('path').resolve(pastaAtual) !== require('path').resolve(cfg.ROOT_DIR)) {
|
|
1613
1740
|
cfg.appendProjectMemory(pastaAtual, `**Pedido:** ${userPrompt.slice(0, 300)}\n**Resultado:** ${finalText.slice(0, 500)}`);
|
|
1614
1741
|
}
|
|
1615
1742
|
|
|
@@ -1639,6 +1766,10 @@ async function runAct(userPrompt, opts) {
|
|
|
1639
1766
|
// de o usuário ter mandado parar.
|
|
1640
1767
|
parado: PARAR,
|
|
1641
1768
|
concluiu,
|
|
1769
|
+
// Onde terminou: o mudar_pasta pode ter trocado no meio.
|
|
1770
|
+
pasta: pastaAtual,
|
|
1771
|
+
// O projeto do Estúdio em que se mexeu, para a memória ir para a pasta dele.
|
|
1772
|
+
estudio: estudioDaTarefa,
|
|
1642
1773
|
};
|
|
1643
1774
|
}
|
|
1644
1775
|
|
|
@@ -1665,6 +1796,7 @@ async function runSubagent(args, ctx) {
|
|
|
1665
1796
|
permissoes,
|
|
1666
1797
|
autorizar: ctx.autorizar,
|
|
1667
1798
|
controle: ctx.controle, // o mesmo objeto: a resposta vale para todos
|
|
1799
|
+
rotulo: `subagente: ${String(descricao).slice(0, 60)}`,
|
|
1668
1800
|
});
|
|
1669
1801
|
|
|
1670
1802
|
/* O que o subagente fez entra no livro de quem o chamou. Sem isto, o
|