primocode 8.14.0 → 8.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 +18 -0
- package/lib/act.js +17 -0
- package/lib/config.js +24 -0
- package/package.json +2 -2
- package/studio/server.py +23 -2
package/bin/primocode.js
CHANGED
|
@@ -425,10 +425,28 @@ function pastaSegura(dir) {
|
|
|
425
425
|
// memory.md dele não carrega neste turno.
|
|
426
426
|
// 3. projeto novo — fica PENDENTE: age em ~/primocode e só cria a pasta se o
|
|
427
427
|
// agente for mexer em arquivo. Pedir para abrir o Chrome não cria projeto.
|
|
428
|
+
// Pedido que se resolve DENTRO do estúdio: vídeo, slides, arte, documento,
|
|
429
|
+
// planilha, diagrama. O que sai dali já é guardado pelo próprio estúdio, em
|
|
430
|
+
// ~/.primocode/studio/arquivos — não precisa (e não deve) virar pasta de
|
|
431
|
+
// código em ~/primocode, cheia de memory.md sobre um vídeo.
|
|
432
|
+
const PEDIDO_DE_ESTUDIO = /\b(v[íi]deo|reels?|anima[çc][ãa]o|slides?|apresenta[çc][ãa]o|deck|post|banner|capa|thumbnail|arte|cartaz|planilha|documento|relat[óo]rio|diagrama|fluxograma|narra[çc][ãa]o|est[úu]dio)\b/i;
|
|
433
|
+
// ...a menos que ele também peça código ou página: aí é projeto de verdade.
|
|
434
|
+
const PEDIDO_DE_CODIGO = /\b(html|css|javascript|js|react|next|python|api|site|p[áa]gina|app|aplicativo|script|reposit[óo]rio|c[óo]digo|programa|servidor|banco de dados)\b/i;
|
|
435
|
+
|
|
436
|
+
function ehSoEstudio(texto) {
|
|
437
|
+
const t = String(texto || '');
|
|
438
|
+
return PEDIDO_DE_ESTUDIO.test(t) && !PEDIDO_DE_CODIGO.test(t);
|
|
439
|
+
}
|
|
440
|
+
|
|
428
441
|
function planWorkDir(userText) {
|
|
429
442
|
if (['/dir', 'detectada', 'projeto'].includes(state.workDirOrigem)) {
|
|
430
443
|
return { dir: state.workDir, pending: false, name: state.currentProject };
|
|
431
444
|
}
|
|
445
|
+
// "Quando eu peço um vídeo, não é pra ficar com projeto. Só vai criar
|
|
446
|
+
// projeto quando tiver um HTML, quando tiver um código."
|
|
447
|
+
if (ehSoEstudio(userText)) {
|
|
448
|
+
return { dir: cfg.ROOT_DIR, pending: false, name: state.currentProject, estudio: true };
|
|
449
|
+
}
|
|
432
450
|
const proposto = deriveProjectName(userText);
|
|
433
451
|
const parecido = cfg.findSimilarProject(proposto);
|
|
434
452
|
if (parecido) return { dir: parecido.dir, pending: false, name: parecido.name, similar: true };
|
package/lib/act.js
CHANGED
|
@@ -668,6 +668,23 @@ async function runAct(userPrompt, opts) {
|
|
|
668
668
|
cfg.appendProjectMemory(pastaAtual, `**Pedido:** ${userPrompt.slice(0, 300)}\n**Resultado:** ${finalText.slice(0, 500)}`);
|
|
669
669
|
}
|
|
670
670
|
|
|
671
|
+
// A memória GERAL é gravada sempre, tenha havido pasta ou não.
|
|
672
|
+
//
|
|
673
|
+
// Antes ela só era escrita se o agente chamasse `remember_general` — e ele
|
|
674
|
+
// quase nunca chamava. Pior: a memória de projeto acima depende de
|
|
675
|
+
// `temPasta`, e tarefa de estúdio (vídeo, slides) não cria pasta nenhuma.
|
|
676
|
+
// Resultado prático: quem só fazia vídeo NUNCA teve memória gravada, de
|
|
677
|
+
// tipo nenhum. O usuário notou: "nunca atualiza os arquivos de memória, e
|
|
678
|
+
// ela deve atualizar independente do projeto".
|
|
679
|
+
//
|
|
680
|
+
// Fica curto de propósito — isto entra em todo pedido seguinte. Uma linha
|
|
681
|
+
// por tarefa concluída, e podada por config.js quando passa do limite.
|
|
682
|
+
if (!subagent && concluiu && finalText.trim()) {
|
|
683
|
+
const pedido = userPrompt.replace(/\s+/g, ' ').trim().slice(0, 160);
|
|
684
|
+
const feito = finalText.replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
685
|
+
cfg.appendGeneralMemory(`- **${pedido}** → ${feito}`);
|
|
686
|
+
}
|
|
687
|
+
|
|
671
688
|
return {
|
|
672
689
|
userMessage: { role: 'user', content: userPrompt },
|
|
673
690
|
assistantMessage: { role: 'assistant', content: finalText },
|
package/lib/config.js
CHANGED
|
@@ -165,6 +165,30 @@ function appendGeneralMemory(entry) {
|
|
|
165
165
|
try {
|
|
166
166
|
ensureStructure();
|
|
167
167
|
fs.appendFileSync(GENERAL_MEMORY, `\n### ${today()}\n${entry}\n`, 'utf-8');
|
|
168
|
+
podarMemoriaGeral();
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// A memória geral entra em TODO pedido, então tamanho aqui é custo por
|
|
173
|
+
// chamada. Sem poda ela cresceria para sempre e, ironicamente, seria cortada
|
|
174
|
+
// no meio pelo orçamento de tokens — perdendo justo o mais recente. Podando
|
|
175
|
+
// pelo fim, o que sobrevive é o que vale: o começo escrito à mão pelo usuário
|
|
176
|
+
// e as últimas coisas aprendidas.
|
|
177
|
+
const LIMITE_MEMORIA_GERAL = 12000;
|
|
178
|
+
|
|
179
|
+
function podarMemoriaGeral() {
|
|
180
|
+
try {
|
|
181
|
+
const texto = fs.readFileSync(GENERAL_MEMORY, 'utf-8');
|
|
182
|
+
if (texto.length <= LIMITE_MEMORIA_GERAL) return;
|
|
183
|
+
const blocos = texto.split(/\n(?=### )/);
|
|
184
|
+
const cabeca = blocos.shift() || ''; // o template e o que o usuário escreveu
|
|
185
|
+
let corpo = '';
|
|
186
|
+
// De trás para frente: os mais novos entram primeiro e ficam.
|
|
187
|
+
for (let i = blocos.length - 1; i >= 0; i--) {
|
|
188
|
+
if (cabeca.length + corpo.length + blocos[i].length > LIMITE_MEMORIA_GERAL) break;
|
|
189
|
+
corpo = '\n' + blocos[i] + corpo;
|
|
190
|
+
}
|
|
191
|
+
fs.writeFileSync(GENERAL_MEMORY, cabeca + corpo, 'utf-8');
|
|
168
192
|
} catch {}
|
|
169
193
|
}
|
|
170
194
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.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": {
|
|
@@ -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 && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.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 && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
package/studio/server.py
CHANGED
|
@@ -649,9 +649,16 @@ def validar_doc(modelo, doc):
|
|
|
649
649
|
if problema:
|
|
650
650
|
return problema
|
|
651
651
|
dur = c.get("duracao")
|
|
652
|
-
if dur is not None and not isinstance(dur, (int, float)):
|
|
652
|
+
if dur is not None and (isinstance(dur, bool) or not isinstance(dur, (int, float))):
|
|
653
653
|
return ('cena %d com "duracao" que não é número (veio %r). Use número de segundos: '
|
|
654
654
|
'"duracao": 3' % (i + 1, dur))
|
|
655
|
+
# Duração zero passava: era número, afinal. E um vídeo cujas cenas
|
|
656
|
+
# somam zero é o "vídeo de 0 segundos" que o usuário viu mais de
|
|
657
|
+
# uma vez — arquivo válido, conteúdo inalcançável.
|
|
658
|
+
if isinstance(dur, (int, float)) and not isinstance(dur, bool) and dur <= 0:
|
|
659
|
+
return ('cena %d tem "duracao": %r. Zero (ou negativo) faz o vídeo inteiro durar '
|
|
660
|
+
'nada — foi assim que saiu um vídeo de 0 segundos. Use pelo menos 1.'
|
|
661
|
+
% (i + 1, dur))
|
|
655
662
|
for j, e in enumerate(els):
|
|
656
663
|
if not isinstance(e, dict) or not e.get("tipo"):
|
|
657
664
|
return 'elemento %d da cena %d sem "tipo" (texto, imagem, lista, numeros, forma...)' % (j + 1, i + 1)
|
|
@@ -1265,7 +1272,21 @@ class Handler(SimpleHTTPRequestHandler):
|
|
|
1265
1272
|
problema = validar_doc(
|
|
1266
1273
|
POR_SLUG.get(a.get("ferramenta"), {}).get("modelo"), novo)
|
|
1267
1274
|
if problema:
|
|
1268
|
-
|
|
1275
|
+
# As frases de validar_doc foram escritas para a CRIAÇÃO:
|
|
1276
|
+
# elas terminam em "crie de novo" e "refaça". Devolver isso
|
|
1277
|
+
# numa ATUALIZAÇÃO manda o agente fazer exatamente a coisa
|
|
1278
|
+
# errada — e foi o que aconteceu: ele recebia o erro, jogava
|
|
1279
|
+
# fora o vídeo pronto e recomeçava do zero, às vezes saindo
|
|
1280
|
+
# pior. Aqui a instrução é a oposta, e vem por último para
|
|
1281
|
+
# ser a última coisa que ele lê.
|
|
1282
|
+
problema = re.sub(r"(crie de novo|e refaça)\.?", "", problema).strip()
|
|
1283
|
+
return self.js({
|
|
1284
|
+
"erro": problema
|
|
1285
|
+
+ ' Isto é uma ATUALIZAÇÃO: NÃO crie outro arquivo e NÃO recomece do zero. '
|
|
1286
|
+
'Corrija só o que está errado acima e chame studio_atualizar de novo '
|
|
1287
|
+
'com o MESMO id "%s". O arquivo continua intacto — nada foi perdido.' % a["id"],
|
|
1288
|
+
"id": a["id"],
|
|
1289
|
+
}, 400)
|
|
1269
1290
|
|
|
1270
1291
|
preparar_voz(novo)
|
|
1271
1292
|
versao = guardar_versao(a)
|