primocode 7.14.1 → 7.15.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 +19 -1
- package/lib/act.js +19 -2
- package/lib/streaming.js +13 -5
- package/package.json +1 -1
package/bin/primocode.js
CHANGED
|
@@ -19,7 +19,7 @@ const { escolherModo } = require('../lib/modo.js');
|
|
|
19
19
|
const api = require('../lib/api');
|
|
20
20
|
const tools = require('../lib/tools');
|
|
21
21
|
const cfg = require('../lib/config');
|
|
22
|
-
const { runAct } = require('../lib/act');
|
|
22
|
+
const { runAct, interromper } = require('../lib/act');
|
|
23
23
|
const pasta = require('../lib/pasta');
|
|
24
24
|
const catalogo = require('../lib/catalogo');
|
|
25
25
|
const streaming = require('../lib/streaming');
|
|
@@ -779,7 +779,25 @@ async function main() {
|
|
|
779
779
|
// Limpa resto da linha (fantasma antigo) e desenha o novo, voltando o cursor.
|
|
780
780
|
process.stdout.write('\x1b[0K' + (fantasma ? `\x1b[2m${fantasma}\x1b[0m\x1b[${fantasma.length}D` : ''));
|
|
781
781
|
};
|
|
782
|
+
// ── Esc Esc: parar o agente no meio do trabalho ──
|
|
783
|
+
// Um Esc avisa; o segundo (em até 2s) manda parar. Dois Escs tão
|
|
784
|
+
// rápidos que chegam colados num chunk só viram UMA tecla com
|
|
785
|
+
// sequence "\x1b\x1b" — vale como os dois.
|
|
786
|
+
let ultimoEsc = 0;
|
|
782
787
|
process.stdin.on('keypress', (ch, key) => {
|
|
788
|
+
if (key && (key.name === 'escape' || key.sequence === '\x1b\x1b')) {
|
|
789
|
+
if (!busy) { ultimoEsc = 0; return; } // sem agente rodando, Esc é só Esc
|
|
790
|
+
const agora = Date.now();
|
|
791
|
+
if (key.sequence === '\x1b\x1b' || agora - ultimoEsc < 2000) {
|
|
792
|
+
ultimoEsc = 0;
|
|
793
|
+
interromper();
|
|
794
|
+
process.stdout.write('\n' + c.warn(' ⏹ parando o agente…') + '\n');
|
|
795
|
+
} else {
|
|
796
|
+
ultimoEsc = agora;
|
|
797
|
+
process.stdout.write('\n' + c.dim(' Esc de novo para parar o agente') + '\n');
|
|
798
|
+
}
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
783
801
|
// Seta → no fim da linha aceita a sugestão inteira.
|
|
784
802
|
if (key && key.name === 'right' && fantasma && rl && rl.cursor === rl.line.length) {
|
|
785
803
|
const resto = fantasma;
|
package/lib/act.js
CHANGED
|
@@ -23,6 +23,14 @@ const painel = require('./streaming');
|
|
|
23
23
|
const MAX_ITERATIONS = 15;
|
|
24
24
|
const MAX_PROFUNDIDADE = 2; // principal + 1 nível de subagente
|
|
25
25
|
|
|
26
|
+
// ── Interrupção (Esc Esc) ─────────────────────────────────────────────────
|
|
27
|
+
// O usuário aperta Esc duas vezes e o agente PARA — entre uma rodada e outra
|
|
28
|
+
// e entre uma tool e a seguinte (uma tool já em execução termina; o que ela
|
|
29
|
+
// fez, ficou feito). A flag é do módulo de propósito: parar o principal para
|
|
30
|
+
// os subagentes junto.
|
|
31
|
+
let PARAR = false;
|
|
32
|
+
function interromper() { PARAR = true; }
|
|
33
|
+
|
|
26
34
|
function ident(profundidade) {
|
|
27
35
|
return ' '.repeat(profundidade);
|
|
28
36
|
}
|
|
@@ -190,7 +198,10 @@ async function runAct(userPrompt, opts) {
|
|
|
190
198
|
// instrução de gente.
|
|
191
199
|
if (!subagent) { try { painel.instrucao(userPrompt); } catch {} }
|
|
192
200
|
|
|
193
|
-
|
|
201
|
+
// Pedido novo zera o pedido de parada anterior.
|
|
202
|
+
if (!subagent) PARAR = false;
|
|
203
|
+
|
|
204
|
+
for (let iteration = 0; iteration < MAX_ITERATIONS && !PARAR; iteration++) {
|
|
194
205
|
const sp = new Spinner(subagent ? 'subagente trabalhando' : 'Primo Code pensando').start();
|
|
195
206
|
|
|
196
207
|
const toolCalls = [];
|
|
@@ -251,6 +262,7 @@ async function runAct(userPrompt, opts) {
|
|
|
251
262
|
|
|
252
263
|
let finished = null;
|
|
253
264
|
for (const tc of toolCalls) {
|
|
265
|
+
if (PARAR) break;
|
|
254
266
|
if (tc.name === 'finish') {
|
|
255
267
|
// Encerrar com falha pendente é como o agente dizia "pronto!"
|
|
256
268
|
// sem ter mudado nada. Devolvemos a tarefa para ele.
|
|
@@ -306,6 +318,11 @@ async function runAct(userPrompt, opts) {
|
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
320
|
|
|
321
|
+
if (PARAR && !finalText) {
|
|
322
|
+
finalText = 'Parado por você (Esc Esc). O que já foi feito, ficou feito.';
|
|
323
|
+
if (!subagent) console.log('\n' + c.warn('⏹ ' + finalText));
|
|
324
|
+
}
|
|
325
|
+
|
|
309
326
|
// Nunca terminar em silêncio. Quando o modelo devolvia resposta vazia sem
|
|
310
327
|
// chamar tool, o usuário via a tela parar e mais nada — pior que erro, porque
|
|
311
328
|
// não dá nem para saber que acabou. Medido: um pedido de navegador morria assim
|
|
@@ -353,4 +370,4 @@ async function runSubagent(args, ctx) {
|
|
|
353
370
|
return resumo;
|
|
354
371
|
}
|
|
355
372
|
|
|
356
|
-
module.exports = { runAct };
|
|
373
|
+
module.exports = { runAct, interromper };
|
package/lib/streaming.js
CHANGED
|
@@ -274,10 +274,11 @@ function capturar(entrada) {
|
|
|
274
274
|
|
|
275
275
|
// O mascote do CLI (▐▛███▜▌ …) redesenhado em SVG, célula a célula, na mesma
|
|
276
276
|
// paleta: bloco cheio na cor viva, contorno na apagada.
|
|
277
|
+
// Olhos e pés têm classe própria: a página anima (piscada e passinhos) por CSS.
|
|
277
278
|
const MASCOTE_SVG = `<svg viewBox="0 0 9 3.6" aria-hidden="true">
|
|
278
279
|
<g fill="#0891b2"><rect x="1" y="0" width="1" height="1"/><rect x="7" y="0" width="1" height="1"/><rect x="0" y="1" width="1" height="1" opacity=".7"/><rect x="8" y="1" width="1" height="1" opacity=".7"/><rect x="1" y="1" width="1" height="1"/><rect x="7" y="1" width="1" height="1"/></g>
|
|
279
|
-
<g fill="#22d3ee"><rect x="2" y="0" width="5" height="1" rx=".12"/><rect x="2" y="1" width="5" height="1"/><rect x="1.9" y="2.2" width=".9" height=".5" rx=".1"/><rect x="3.4" y="2.2" width=".9" height=".5" rx=".1"/><rect x="4.9" y="2.2" width=".9" height=".5" rx=".1"/><rect x="6.4" y="2.2" width=".9" height=".5" rx=".1"/></g>
|
|
280
|
-
<g fill="#0b1220"><rect x="3" y=".35" width=".8" height=".5" rx=".1"/><rect x="5.2" y=".35" width=".8" height=".5" rx=".1"/><rect x="3.9" y="1.25" width="1.2" height=".28" rx=".14"/></g>
|
|
280
|
+
<g fill="#22d3ee"><rect x="2" y="0" width="5" height="1" rx=".12"/><rect x="2" y="1" width="5" height="1"/><rect class="pe pe1" x="1.9" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe2" x="3.4" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe1" x="4.9" y="2.2" width=".9" height=".5" rx=".1"/><rect class="pe pe2" x="6.4" y="2.2" width=".9" height=".5" rx=".1"/></g>
|
|
281
|
+
<g fill="#0b1220"><rect class="olho" x="3" y=".35" width=".8" height=".5" rx=".1"/><rect class="olho" x="5.2" y=".35" width=".8" height=".5" rx=".1"/><rect x="3.9" y="1.25" width="1.2" height=".28" rx=".14"/></g>
|
|
281
282
|
</svg>`;
|
|
282
283
|
|
|
283
284
|
// A tela do painel, na identidade do app — a MESMA paleta do design system do
|
|
@@ -307,9 +308,16 @@ const PAGINA = `<!doctype html>
|
|
|
307
308
|
|
|
308
309
|
/* ── Cabeçalho: mascote inclinado + nome, como no desenho ── */
|
|
309
310
|
#topo { position: relative; width: min(1120px, 100%); padding: 44px 0 26px; text-align: center; }
|
|
310
|
-
#topo .mascote { position: absolute; left: 4px; top:
|
|
311
|
-
@keyframes flutua {
|
|
312
|
-
#topo .mascote svg { width: 100%; height: 100%; display: block; }
|
|
311
|
+
#topo .mascote { position: absolute; left: 4px; top: 24px; width: 104px; height: 44px; filter: drop-shadow(0 8px 20px rgba(34,211,238,.4)); animation: flutua 2.8s ease-in-out infinite; }
|
|
312
|
+
@keyframes flutua { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(6px) } }
|
|
313
|
+
#topo .mascote svg { width: 100%; height: 100%; display: block; overflow: visible; }
|
|
314
|
+
/* vivo: pisca os olhos de vez em quando e dá passinhos com os pés */
|
|
315
|
+
.mascote .olho { transform-box: fill-box; transform-origin: center; animation: piscaOlho 3.6s infinite; }
|
|
316
|
+
@keyframes piscaOlho { 0%, 89%, 100% { transform: scaleY(1) } 93%, 95% { transform: scaleY(.12) } }
|
|
317
|
+
.mascote .pe { transform-box: fill-box; transform-origin: center; }
|
|
318
|
+
.mascote .pe1 { animation: passinho 1.4s ease-in-out infinite; }
|
|
319
|
+
.mascote .pe2 { animation: passinho 1.4s ease-in-out infinite; animation-delay: .7s; }
|
|
320
|
+
@keyframes passinho { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-.14px) } }
|
|
313
321
|
#topo h1 { font-size: clamp(34px, 5.6vw, 54px); font-weight: 650; letter-spacing: .4px; color: #f1f5f9; text-shadow: 0 4px 28px rgba(34,211,238,.22); }
|
|
314
322
|
#topo h1 span { color: #22d3ee; }
|
|
315
323
|
#topo .vivo { position: absolute; right: 2px; top: 48px; display: inline-flex; align-items: center; gap: 8px; color: #94a3b8; font-size: 12.5px; background: rgba(15,23,42,.85); border: 1px solid #1e293b; border-radius: 999px; padding: 6px 14px; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.15.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": {
|