primocode 8.6.0 → 8.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/primocode.js +58 -13
- package/lib/act.js +99 -1
- package/lib/api.js +7 -1
- package/package.json +2 -2
package/bin/primocode.js
CHANGED
|
@@ -542,19 +542,35 @@ async function sendPrompt(userText) {
|
|
|
542
542
|
// que está torto, diz qual é o entregável e o que conta como pronto.
|
|
543
543
|
const prompt2 = await refinarPedido(prompt);
|
|
544
544
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
545
|
+
// A tarefa grande vira passos, e cada passo é uma chamada própria — com
|
|
546
|
+
// conversa nova, e por isso pequena. Oculto: o usuário mandou UMA coisa.
|
|
547
|
+
const passos = await dividirTarefa(prompt2);
|
|
548
|
+
|
|
549
|
+
let r = null;
|
|
550
|
+
let jaFeito = '';
|
|
551
|
+
for (let i = 0; i < passos.length; i++) {
|
|
552
|
+
if (i > 0) console.log(c.dim(` ⎿ ${passos[i].titulo}`));
|
|
553
|
+
const pedido = i === 0 ? passos[i].pedido
|
|
554
|
+
: `${passos[i].pedido}\n\nO QUE JÁ FOI FEITO ANTES (use estes fatos, não pesquise de novo):\n${jaFeito}`;
|
|
555
|
+
|
|
556
|
+
r = await runAct(pedido, {
|
|
557
|
+
server: state.config.server,
|
|
558
|
+
model: state.config.model,
|
|
559
|
+
effort: state.config.effort,
|
|
560
|
+
token: state.config.token,
|
|
561
|
+
// Só o primeiro passo enxerga a conversa: os seguintes recebem o
|
|
562
|
+
// que interessa em `jaFeito`, e conversa a mais só ocupa o teto.
|
|
563
|
+
history: i === 0 ? memoria.paraEnviar(state.memoria, state.history) : [],
|
|
564
|
+
projectDir: plan.dir,
|
|
565
|
+
projectName: plan.name || state.currentProject,
|
|
566
|
+
// Só chamado se o agente for mesmo MEXER EM ARQUIVO — abrir o Chrome
|
|
567
|
+
// não é motivo para criar pasta de projeto.
|
|
568
|
+
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
569
|
+
motor: state.engine === 'claude-code' ? claudeEngine : api,
|
|
570
|
+
});
|
|
571
|
+
jaFeito = String((r.assistantMessage && r.assistantMessage.content) || '').slice(0, 1200);
|
|
572
|
+
if (r.parado) break; // Esc Esc no meio do plano para o plano inteiro
|
|
573
|
+
}
|
|
558
574
|
state.history.push({ role: 'user', content: userText });
|
|
559
575
|
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
560
576
|
state.lastResponse = r.assistantMessage.content;
|
|
@@ -568,6 +584,35 @@ async function sendPrompt(userText) {
|
|
|
568
584
|
refreshPrompt();
|
|
569
585
|
}
|
|
570
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Divide a tarefa grande em passos — e faz isso ESCONDIDO.
|
|
589
|
+
*
|
|
590
|
+
* O motivo é aritmético. Com 6.000 tokens por minuto, "pesquise sobre a X e
|
|
591
|
+
* crie um vídeo" não cabe numa conversa só: o custo fixo (prompt + tools) é
|
|
592
|
+
* ~3.700, a spec do formato ~1.400, os fatos da pesquisa o resto — e sobram
|
|
593
|
+
* 512 para o agente escrever o documento. Em DOIS pedidos cada um cabe
|
|
594
|
+
* folgado, porque cada um leva só as ferramentas e o prompt do seu passo.
|
|
595
|
+
*
|
|
596
|
+
* O dono queria isto automático: "o usuário manda a tarefa gigante, e a IA
|
|
597
|
+
* ocultamente vai quebrar a tarefa". E com razão — quem pede não sabe onde
|
|
598
|
+
* está a fronteira barata, e pode partir num pedaço que ainda não cabe.
|
|
599
|
+
*
|
|
600
|
+
* Falhou? Um passo só, que é o comportamento de sempre.
|
|
601
|
+
*/
|
|
602
|
+
async function dividirTarefa(pedido) {
|
|
603
|
+
const inteiro = [{ titulo: 'tarefa', pedido }];
|
|
604
|
+
try {
|
|
605
|
+
const r = await api.requestPlano(state.config.server, {
|
|
606
|
+
prompt: pedido, model: state.config.model, token: state.config.token,
|
|
607
|
+
});
|
|
608
|
+
const passos = (r && Array.isArray(r.passos) && r.passos.length) ? r.passos : inteiro;
|
|
609
|
+
if (passos.length > 1) console.log(c.dim(` ⎿ ${passos[0].titulo}`));
|
|
610
|
+
return passos;
|
|
611
|
+
} catch {
|
|
612
|
+
return inteiro;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
571
616
|
/**
|
|
572
617
|
* Estrutura o pedido antes de mandar o agente executar.
|
|
573
618
|
*
|
package/lib/act.js
CHANGED
|
@@ -37,6 +37,45 @@ function interromper() {
|
|
|
37
37
|
if (emVoo) { try { emVoo.abort(); } catch {} }
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Este erro é "não coube no minuto"?
|
|
42
|
+
*
|
|
43
|
+
* É o único erro que a gente sabe consertar sozinho: encolher o que já foi
|
|
44
|
+
* feito e continuar. Rede, cota do dia e chave inválida não se resolvem
|
|
45
|
+
* assim, e tentar de novo só gastaria a vez do usuário.
|
|
46
|
+
*/
|
|
47
|
+
function ehTetoDeToken(e) {
|
|
48
|
+
const s = String((e && e.message) || e || '').toLowerCase();
|
|
49
|
+
return /não cabe no teto|conversa passou do teto|request too large|tokens per minute|\btpm\b|reduce your message size/.test(s);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* O /compact automático, feito no meio da tarefa.
|
|
54
|
+
*
|
|
55
|
+
* O dono: "quando bate no teto dos 6 mil, ele roda o compact ocultamente e
|
|
56
|
+
* continua a tarefa, pega todo o contexto que ele fez antes, como se nada
|
|
57
|
+
* tivesse acontecido".
|
|
58
|
+
*
|
|
59
|
+
* Troca as rodadas já feitas por UMA anotação do que se descobriu e do que
|
|
60
|
+
* ficou pronto. O agente perde o texto literal das ferramentas e mantém o que
|
|
61
|
+
* importa para seguir: onde estava, o que já sabe, o que falta.
|
|
62
|
+
*/
|
|
63
|
+
function anotacaoDoQueJaFoi(messages) {
|
|
64
|
+
const chamadas = repeticao.chamadasPorId(messages);
|
|
65
|
+
const feitos = [];
|
|
66
|
+
for (const m of messages) {
|
|
67
|
+
if (!m || m.role !== 'tool') continue;
|
|
68
|
+
const ch = chamadas.get(m.tool_call_id);
|
|
69
|
+
if (!ch) continue;
|
|
70
|
+
const alvo = ch.args && (ch.args.path || ch.args.consulta || ch.args.ferramenta || ch.args.titulo || ch.args.id);
|
|
71
|
+
const corpo = String(m.content || '');
|
|
72
|
+
const deuErrado = /"ok"\s*:\s*false/.test(corpo);
|
|
73
|
+
feitos.push(`- ${ch.nome}${alvo ? `(${String(alvo).slice(0, 60)})` : ''}: `
|
|
74
|
+
+ (deuErrado ? 'FALHOU' : corpo.slice(0, 220).replace(/\s+/g, ' ')));
|
|
75
|
+
}
|
|
76
|
+
return feitos;
|
|
77
|
+
}
|
|
78
|
+
|
|
40
79
|
function ident(profundidade) {
|
|
41
80
|
return ' '.repeat(profundidade);
|
|
42
81
|
}
|
|
@@ -231,6 +270,29 @@ function compactar(messages) {
|
|
|
231
270
|
* murro em ponta de faca. Dizer o motivo certo é a diferença entre o usuário
|
|
232
271
|
* saber o que fazer e ficar no escuro.
|
|
233
272
|
*/
|
|
273
|
+
/**
|
|
274
|
+
* Troca as rodadas já feitas por uma ANOTAÇÃO do que se descobriu, no lugar.
|
|
275
|
+
*
|
|
276
|
+
* Mexe no array recebido de propósito: quem chamou continua o mesmo loop, com
|
|
277
|
+
* a mesma conversa, só que leve. O pedido do usuário e a mensagem de sistema
|
|
278
|
+
* ficam — sem eles o agente não sabe mais o que estava fazendo.
|
|
279
|
+
*/
|
|
280
|
+
function encolherParaContinuar(messages) {
|
|
281
|
+
const anotacao = anotacaoDoQueJaFoi(messages);
|
|
282
|
+
const guardar = messages.filter((m) => m.role === 'system' || m.role === 'user');
|
|
283
|
+
messages.length = 0;
|
|
284
|
+
messages.push(...guardar);
|
|
285
|
+
if (anotacao.length) {
|
|
286
|
+
messages.push({
|
|
287
|
+
role: 'system',
|
|
288
|
+
content: 'O QUE VOCÊ JÁ FEZ NESTA TAREFA (a conversa foi encolhida para caber no limite; '
|
|
289
|
+
+ 'estas são as suas anotações, continue de onde parou e NÃO refaça o que já está aqui):\n'
|
|
290
|
+
+ anotacao.join('\n')
|
|
291
|
+
+ '\n\nSiga para o próximo passo. Se já tem o que precisa, PRODUZA o resultado agora.',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
234
296
|
function motivoDaParada({ voltas, voltasEmFalso, falhasPendentes, travou }) {
|
|
235
297
|
if (falhasPendentes.length) {
|
|
236
298
|
return `Parei sem concluir. A última falha foi: ${falhasPendentes[falhasPendentes.length - 1].texto}`;
|
|
@@ -294,6 +356,12 @@ async function runAct(userPrompt, opts) {
|
|
|
294
356
|
// Pedido novo zera o pedido de parada anterior.
|
|
295
357
|
if (!subagent) PARAR = false;
|
|
296
358
|
|
|
359
|
+
// Quantas vezes já encolhemos por estouro de teto. Duas bastam: se depois
|
|
360
|
+
// disso ainda não couber, o problema é o custo fixo, e encolher de novo só
|
|
361
|
+
// apagaria o trabalho sem resolver.
|
|
362
|
+
let compactacoes = 0;
|
|
363
|
+
const MAX_COMPACTACOES = 2;
|
|
364
|
+
|
|
297
365
|
let voltas = 0;
|
|
298
366
|
for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
|
|
299
367
|
voltas++;
|
|
@@ -318,6 +386,20 @@ async function runAct(userPrompt, opts) {
|
|
|
318
386
|
});
|
|
319
387
|
} catch (e) {
|
|
320
388
|
sp.stop();
|
|
389
|
+
emVoo = null;
|
|
390
|
+
// Estourou o minuto: encolhe o que já foi feito e CONTINUA, em vez
|
|
391
|
+
// de devolver erro e jogar fora o trabalho. O usuário não precisa
|
|
392
|
+
// saber que isso aconteceu — a tarefa é dele, o encanamento é nosso.
|
|
393
|
+
if (ehTetoDeToken(e) && compactacoes < MAX_COMPACTACOES && !PARAR) {
|
|
394
|
+
compactacoes++;
|
|
395
|
+
const antes = messages.length;
|
|
396
|
+
encolherParaContinuar(messages);
|
|
397
|
+
if (!subagent) {
|
|
398
|
+
console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
|
|
399
|
+
}
|
|
400
|
+
iteration--; // esta volta não contou: nada foi decidido
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
321
403
|
const amigavel = friendlyError(e);
|
|
322
404
|
if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
|
|
323
405
|
console.log('\n' + c.err(amigavel));
|
|
@@ -327,6 +409,17 @@ async function runAct(userPrompt, opts) {
|
|
|
327
409
|
emVoo = null;
|
|
328
410
|
if (PARAR) break;
|
|
329
411
|
|
|
412
|
+
if (streamError && ehTetoDeToken(streamError) && compactacoes < MAX_COMPACTACOES && !PARAR) {
|
|
413
|
+
compactacoes++;
|
|
414
|
+
const antes = messages.length;
|
|
415
|
+
encolherParaContinuar(messages);
|
|
416
|
+
if (!subagent) {
|
|
417
|
+
console.log(ident(profundidade) + c.dim(` ⎿ conversa encolhida para continuar (${antes} → ${messages.length} mensagens)`));
|
|
418
|
+
}
|
|
419
|
+
iteration--;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
330
423
|
if (streamError) {
|
|
331
424
|
const amigavel = friendlyError(streamError);
|
|
332
425
|
if (subagent) return { assistantMessage: { role: 'assistant', content: `Subagente falhou: ${amigavel}` } };
|
|
@@ -497,6 +590,10 @@ async function runAct(userPrompt, opts) {
|
|
|
497
590
|
return {
|
|
498
591
|
userMessage: { role: 'user', content: userPrompt },
|
|
499
592
|
assistantMessage: { role: 'assistant', content: finalText },
|
|
593
|
+
// Esc Esc no meio de um plano de vários passos precisa parar o plano
|
|
594
|
+
// INTEIRO, não só o passo — senão o próximo passo começa logo depois
|
|
595
|
+
// de o usuário ter mandado parar.
|
|
596
|
+
parado: PARAR,
|
|
500
597
|
};
|
|
501
598
|
}
|
|
502
599
|
|
|
@@ -524,4 +621,5 @@ async function runSubagent(args, ctx) {
|
|
|
524
621
|
return resumo;
|
|
525
622
|
}
|
|
526
623
|
|
|
527
|
-
module.exports = { runAct, interromper, compactar, motivoDaParada,
|
|
624
|
+
module.exports = { runAct, interromper, compactar, motivoDaParada, ehTetoDeToken,
|
|
625
|
+
anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
|
package/lib/api.js
CHANGED
|
@@ -162,6 +162,12 @@ function requestRefino(serverUrl, payload) {
|
|
|
162
162
|
return request(serverUrl, '/api/refinar', body, token);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// O plano: a tarefa grande dividida em passos, para uma chamada por pedaço.
|
|
166
|
+
function requestPlano(serverUrl, payload) {
|
|
167
|
+
const { token, ...body } = payload;
|
|
168
|
+
return request(serverUrl, '/api/plano', body, token);
|
|
169
|
+
}
|
|
170
|
+
|
|
165
171
|
function getHealth(serverUrl, token) {
|
|
166
172
|
return new Promise((resolve, reject) => {
|
|
167
173
|
const url = new URL('/health', serverUrl);
|
|
@@ -178,4 +184,4 @@ function getHealth(serverUrl, token) {
|
|
|
178
184
|
});
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, getHealth };
|
|
187
|
+
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, getHealth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Modelos grátis. Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|