primocode 8.34.1 → 8.36.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/app/index.html +2 -2
- package/bin/primocode.js +151 -69
- package/lib/act.js +68 -0
- package/lib/browser.js +292 -1
- package/lib/catalogo.js +11 -2
- package/lib/claude-engine.js +25 -2
- package/lib/codex-engine.js +9 -2
- package/lib/icone.js +50 -34
- package/lib/memoria-conversa.js +24 -1
- package/lib/pipeline.js +447 -0
- package/lib/studio.js +16 -3
- package/lib/tools.js +2 -1
- package/package.json +2 -2
- package/studio/ferramentas/corte.md +73 -10
- package/studio/server.py +78 -4
- package/studio/web/js/analise.js +319 -11
- package/studio/web/js/audio.js +367 -55
- package/studio/web/js/render.js +109 -2
- package/studio/web/js/video.js +4 -0
package/app/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<div class="rotulo">Conversas</div>
|
|
41
41
|
<div class="recentes" id="recentes">
|
|
42
42
|
<div class="vazio-lista" id="listaVazia">
|
|
43
|
-
As conversas aparecem aqui quando você entra na sua conta com <code>/
|
|
43
|
+
As conversas aparecem aqui quando você entra na sua conta com <code>/connect</code>.
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
<p>Escreva do jeito que você falaria. Ele lê a pasta, escreve o código,
|
|
70
70
|
roda o que precisar e conta o que fez.</p>
|
|
71
71
|
<div class="sugestoes">
|
|
72
|
-
<button class="sugestao" data-manda="/
|
|
72
|
+
<button class="sugestao" data-manda="/all">o que ele sabe fazer</button>
|
|
73
73
|
<button class="sugestao" data-manda="/project">meus projetos</button>
|
|
74
74
|
<button class="sugestao" data-manda="/effort">esforço</button>
|
|
75
75
|
<button class="sugestao" data-manda="/help">comandos</button>
|
package/bin/primocode.js
CHANGED
|
@@ -82,31 +82,32 @@ let janelaAberta = null;
|
|
|
82
82
|
|
|
83
83
|
const HELP = `
|
|
84
84
|
${c.bold('Comandos')}
|
|
85
|
-
${c.brand('/dir')} <caminho>
|
|
86
|
-
${c.brand('/project')}
|
|
87
|
-
${c.brand('/mode')}
|
|
88
|
-
${c.brand('/effort')}
|
|
89
|
-
${c.brand('/
|
|
90
|
-
${c.brand('/
|
|
91
|
-
${c.brand('/
|
|
92
|
-
${c.brand('/
|
|
93
|
-
${c.brand('/
|
|
94
|
-
${c.brand('/
|
|
95
|
-
${c.brand('/
|
|
96
|
-
${c.brand('/
|
|
97
|
-
${c.brand('/
|
|
98
|
-
${c.brand('/
|
|
99
|
-
${c.brand('/
|
|
100
|
-
${c.brand('/
|
|
101
|
-
${c.brand('/
|
|
102
|
-
${c.brand('/
|
|
103
|
-
${c.brand('/
|
|
104
|
-
${c.brand('/
|
|
105
|
-
${c.brand('/
|
|
106
|
-
${c.brand('/
|
|
107
|
-
${c.brand('/
|
|
108
|
-
${c.brand('/
|
|
109
|
-
${c.brand('/
|
|
85
|
+
${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
|
|
86
|
+
${c.brand('/project')} lista projetos ou cria em ~/primocode: /project <nome>
|
|
87
|
+
${c.brand('/mode')} /mode primocode|chat|auto
|
|
88
|
+
${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
|
|
89
|
+
${c.brand('/compact')} resume a conversa e devolve o contexto
|
|
90
|
+
${c.brand('/attach')} <arquivo> anexa arquivo ao próximo pedido
|
|
91
|
+
${c.brand('/files')} lista anexos ${c.brand('/detach')} remove
|
|
92
|
+
${c.brand('/all')} lista tudo que ele sabe fazer
|
|
93
|
+
${c.brand('/tree')} [n] árvore do projeto
|
|
94
|
+
${c.brand('/read')} <arquivo> mostra um arquivo
|
|
95
|
+
${c.brand('/run')} <comando> executa no shell do projeto
|
|
96
|
+
${c.brand('/open')} <arq|url> abre no navegador
|
|
97
|
+
${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
|
|
98
|
+
${c.brand('/claude')} usa o Claude Code da SUA conta como motor (Opus)
|
|
99
|
+
${c.brand('/gpt')} usa o GPT (Codex) da SUA conta como motor
|
|
100
|
+
${c.brand('/key')} <provedor> sua própria chave de API (Claude, GPT)
|
|
101
|
+
${c.brand('/connect')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
102
|
+
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
103
|
+
${c.brand('/logout')} desconecta este computador da conta
|
|
104
|
+
${c.brand('/status')} checa o servidor
|
|
105
|
+
${c.brand('/name')} <nome> como o Primo te chama
|
|
106
|
+
${c.brand('/mascot')} <estilo> mascote: primo|fox|cat
|
|
107
|
+
${c.brand('/config')} mostra suas preferências
|
|
108
|
+
${c.brand('/clear')} limpa a conversa ${c.brand('/cls')} limpa a tela
|
|
109
|
+
${c.brand('/uninstall')} remove o PrimoCode
|
|
110
|
+
${c.brand('/exit')} sai do PrimoCode
|
|
110
111
|
|
|
111
112
|
${c.bold('Modos')}
|
|
112
113
|
${c.warn('Primo Code')} ${c.muted('Agente completo — cria arquivos, roda comandos, abre o navegador')}
|
|
@@ -131,10 +132,11 @@ const SLASH_COMANDOS = [
|
|
|
131
132
|
['/dir', 'trabalhar numa pasta sua'],
|
|
132
133
|
['/mode', 'primocode | chat | auto'],
|
|
133
134
|
['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
|
|
135
|
+
['/compact', 'resume a conversa e devolve o contexto'],
|
|
134
136
|
['/attach', 'anexa arquivo ao pedido'],
|
|
135
137
|
['/detach', 'remove os anexos'],
|
|
136
138
|
['/files', 'lista os anexos'],
|
|
137
|
-
['/
|
|
139
|
+
['/all', 'tudo que ele sabe fazer'],
|
|
138
140
|
['/tree', 'árvore do projeto'],
|
|
139
141
|
['/read', 'mostra um arquivo'],
|
|
140
142
|
['/run', 'roda um comando'],
|
|
@@ -142,11 +144,11 @@ const SLASH_COMANDOS = [
|
|
|
142
144
|
['/rc', 'abre a janela do PrimoCode'],
|
|
143
145
|
['/claude', 'usa o Claude Code da sua conta como motor'],
|
|
144
146
|
['/gpt', 'usa o GPT (Codex) da sua conta como motor'],
|
|
145
|
-
['/
|
|
146
|
-
['/
|
|
147
|
-
['/
|
|
148
|
-
['/
|
|
149
|
-
['/
|
|
147
|
+
['/key', 'sua própria chave de API (Claude, Codex)'],
|
|
148
|
+
['/connect', 'entra na sua conta Conecta Primo'],
|
|
149
|
+
['/account', 'sua conta, plano e computadores conectados'],
|
|
150
|
+
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
151
|
+
['/logout', 'desconecta este computador da conta'],
|
|
150
152
|
['/status', 'checa o servidor'],
|
|
151
153
|
['/name', 'como ele te chama'],
|
|
152
154
|
['/mascot', 'primo | fox | cat'],
|
|
@@ -259,8 +261,22 @@ function makePrompt() {
|
|
|
259
261
|
modeTag = state.mode === 'primocode' ? c.warn('Primo Code') : c.brandDim('Chat');
|
|
260
262
|
}
|
|
261
263
|
const proj = state.currentProject ? c.dim(` ${state.currentProject}`) : '';
|
|
264
|
+
|
|
265
|
+
/* O CONTEXTO na barra, como o pedido pede. Só aparece depois que há
|
|
266
|
+
conversa: um "ctx 0%" na abertura é ruído, e ruído que aparece sempre é
|
|
267
|
+
o que a pessoa aprende a não enxergar — inclusive quando marcar 90%.
|
|
268
|
+
Fica âmbar a partir de 70% e vermelho a partir de 100%, que é quando
|
|
269
|
+
`/compact` deixa de ser conselho e vira o que fazer. */
|
|
270
|
+
const uso = memoria.usoDoContexto(state.memoria, state.history);
|
|
271
|
+
const ctx = uso.pct < 5 ? '' : c.dim(' · ')
|
|
272
|
+
+ (uso.pct >= 100 ? c.err(`ctx ${uso.pct}%`)
|
|
273
|
+
: uso.pct >= 70 ? c.warn(`ctx ${uso.pct}%`)
|
|
274
|
+
: c.dim(`ctx ${uso.pct}%`));
|
|
275
|
+
// O modelo ativo: qual motor está pensando. No padrão é o do servidor.
|
|
276
|
+
const motor = state.engine === 'claude-code' ? c.dim(' · ') + c.dim('claude')
|
|
277
|
+
: state.engine === 'codex' ? c.dim(' · ') + c.dim('gpt') : '';
|
|
262
278
|
const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
|
|
263
|
-
return `\n${c.muted(modeTag)}${proj}${att} ${c.brand('❯')} `;
|
|
279
|
+
return `\n${c.muted(modeTag)}${motor}${ctx}${proj}${att} ${c.brand('❯')} `;
|
|
264
280
|
}
|
|
265
281
|
function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
|
|
266
282
|
|
|
@@ -458,7 +474,7 @@ async function sendPrompt(userText) {
|
|
|
458
474
|
if (state.liberado === false) {
|
|
459
475
|
console.log(c.err(' O PrimoCode pede o plano Premium ou Super.'));
|
|
460
476
|
console.log(c.muted(' Assine em ') + c.brand('conecta-primo-ai.vercel.app')
|
|
461
|
-
+ c.muted(' · já assinou? ') + c.brand('/
|
|
477
|
+
+ c.muted(' · já assinou? ') + c.brand('/connect'));
|
|
462
478
|
return;
|
|
463
479
|
}
|
|
464
480
|
|
|
@@ -649,8 +665,12 @@ async function dividirTarefa(pedido) {
|
|
|
649
665
|
* Silencioso quando não há o que fazer: nas primeiras rodadas a conversa é
|
|
650
666
|
* curta e o resumo não pagaria a chamada.
|
|
651
667
|
*/
|
|
652
|
-
|
|
653
|
-
|
|
668
|
+
/* `forcado` é o `/compact`: a pessoa mandou resumir, e mandar resumir tem de
|
|
669
|
+
resumir. Sem isso o comando responderia "nada a fazer" justamente quando
|
|
670
|
+
ela está vendo a IA se perder — e "não fiz nada" é a pior resposta possível
|
|
671
|
+
para quem acabou de pedir socorro. */
|
|
672
|
+
async function comprimirConversa(forcado = false) {
|
|
673
|
+
if (!forcado && !memoria.precisaResumir(state.memoria, state.history)) return;
|
|
654
674
|
const sp = new Spinner('resumindo a conversa').start();
|
|
655
675
|
try {
|
|
656
676
|
state.memoria = await memoria.atualizar(state.memoria, state.history, ({ resumo, mensagens }) =>
|
|
@@ -720,11 +740,49 @@ function motorDaVez() {
|
|
|
720
740
|
|
|
721
741
|
function isSlash(text) { return text.trim().startsWith('/'); }
|
|
722
742
|
|
|
743
|
+
/* ── OS NOMES ANTIGOS ────────────────────────────────────────────────────
|
|
744
|
+
* Os comandos passaram a ser todos em inglês. Metade já era; a outra metade
|
|
745
|
+
* estava em português, e a mistura era o que a especificação chamou de
|
|
746
|
+
* bagunça — `/run` ao lado de `/pasta`, `/tree` ao lado de `/tudo`.
|
|
747
|
+
*
|
|
748
|
+
* O nome antigo NÃO some: ele responde e ensina o novo. Sumir de vez faz quem
|
|
749
|
+
* tem o dedo viciado bater num "comando não existe" e concluir que a
|
|
750
|
+
* atualização quebrou alguma coisa — e essa pessoa não vai procurar no /help,
|
|
751
|
+
* vai desinstalar.
|
|
752
|
+
*
|
|
753
|
+
* Uma tabela, e não um `if` dentro de cada `case`: com dezoito apelidos, o
|
|
754
|
+
* `if` espalhado é o jeito garantido de esquecer um. Aqui, esquecer é não
|
|
755
|
+
* escrever a linha, e a linha some da lista.
|
|
756
|
+
*/
|
|
757
|
+
const RENOMEADOS = {
|
|
758
|
+
// o que abre a janela
|
|
759
|
+
app: 'rc',
|
|
760
|
+
// a conta
|
|
761
|
+
entrar: 'connect', login: 'connect', conectar: 'connect',
|
|
762
|
+
conta: 'account',
|
|
763
|
+
sair: 'exit', // sair é SAIR; desconectar a conta é /logout
|
|
764
|
+
// as chaves de API
|
|
765
|
+
chave: 'key', chaves: 'key',
|
|
766
|
+
// o histórico
|
|
767
|
+
conversas: 'chats', historico: 'chats', histórico: 'chats',
|
|
768
|
+
// o resto
|
|
769
|
+
pasta: 'dir',
|
|
770
|
+
esforco: 'effort', esforço: 'effort',
|
|
771
|
+
tudo: 'all', capacidades: 'all',
|
|
772
|
+
};
|
|
773
|
+
|
|
723
774
|
async function handleSlash(text) {
|
|
724
775
|
const [cmd] = text.trim().slice(1).split(/\s+/);
|
|
725
776
|
const rest = text.trim().slice(1 + cmd.length).trim();
|
|
726
777
|
|
|
727
|
-
|
|
778
|
+
let nome = cmd.toLowerCase();
|
|
779
|
+
if (RENOMEADOS[nome]) {
|
|
780
|
+
const novo = RENOMEADOS[nome];
|
|
781
|
+
console.log(c.muted(' Agora é ') + c.brand('/' + novo) + c.muted('.'));
|
|
782
|
+
nome = novo;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
switch (nome) {
|
|
728
786
|
case 'help': console.log(HELP); break;
|
|
729
787
|
|
|
730
788
|
case 'project': {
|
|
@@ -751,8 +809,7 @@ async function handleSlash(text) {
|
|
|
751
809
|
// Antes só existia /project, que SEMPRE cria em ~/primocode/<nome>. Se a
|
|
752
810
|
// sua pasta já existe em outro lugar, não havia como apontar para ela.
|
|
753
811
|
case 'dir':
|
|
754
|
-
case 'cd':
|
|
755
|
-
case 'pasta': {
|
|
812
|
+
case 'cd': {
|
|
756
813
|
if (!rest) {
|
|
757
814
|
const de = {
|
|
758
815
|
padrao: 'a pasta padrão do PrimoCode',
|
|
@@ -820,7 +877,7 @@ async function handleSlash(text) {
|
|
|
820
877
|
/* A chave de API da própria pessoa. Guardada em ~/.primocode/
|
|
821
878
|
chaves.json com permissão 600 — nunca no config.json, que é o
|
|
822
879
|
arquivo que se cola num chamado de suporte. */
|
|
823
|
-
case '
|
|
880
|
+
case 'key': case 'keys': {
|
|
824
881
|
const [qual, ...resto] = rest.split(/\s+/).filter(Boolean);
|
|
825
882
|
const valor = resto.join(' ');
|
|
826
883
|
|
|
@@ -831,8 +888,8 @@ async function handleSlash(text) {
|
|
|
831
888
|
+ (p.tem ? c.ok(p.disfarce) : c.dim('não configurada'))
|
|
832
889
|
+ c.dim(` ${p.variavel}`));
|
|
833
890
|
}
|
|
834
|
-
console.log(c.dim('\n /
|
|
835
|
-
console.log(c.dim(' /
|
|
891
|
+
console.log(c.dim('\n /key claude sk-ant-… guarda a chave'));
|
|
892
|
+
console.log(c.dim(' /key gpt remover apaga a que estiver lá'));
|
|
836
893
|
console.log(c.muted('\n Sem chave própria, cada motor usa a conta em que você já entrou nele.'));
|
|
837
894
|
break;
|
|
838
895
|
}
|
|
@@ -848,7 +905,7 @@ async function handleSlash(text) {
|
|
|
848
905
|
}
|
|
849
906
|
if (!valor) {
|
|
850
907
|
const p = chaves.PROVEDORES[qual];
|
|
851
|
-
console.log(c.muted(` Uso: `) + c.brand(`/
|
|
908
|
+
console.log(c.muted(` Uso: `) + c.brand(`/key ${qual} ${p.prefixo}…`));
|
|
852
909
|
console.log(c.dim(` Pegue a sua em ${p.onde}`));
|
|
853
910
|
break;
|
|
854
911
|
}
|
|
@@ -911,7 +968,7 @@ async function handleSlash(text) {
|
|
|
911
968
|
// O /model virou /effort: você escolhe o ESFORÇO num slider (←/→,
|
|
912
969
|
// Enter confirma, Esc cancela) e o modelo sai daí. Com argumento
|
|
913
970
|
// ("/effort high") aplica direto, para quem já sabe o que quer.
|
|
914
|
-
case 'effort':
|
|
971
|
+
case 'effort': {
|
|
915
972
|
const pedido = rest.toLowerCase().trim();
|
|
916
973
|
if (pedido) {
|
|
917
974
|
if (!effort.NOMES.includes(pedido)) {
|
|
@@ -964,9 +1021,8 @@ async function handleSlash(text) {
|
|
|
964
1021
|
// A lista vem de lib/catalogo.js, e o test/catalogo.test.js cruza aquela
|
|
965
1022
|
// lista com as tools que o tools.execute realmente atende. Ou seja: esta
|
|
966
1023
|
// tela não consegue prometer coisa que o programa não faz.
|
|
967
|
-
case '
|
|
968
|
-
case 'tools':
|
|
969
|
-
case 'capacidades': {
|
|
1024
|
+
case 'all':
|
|
1025
|
+
case 'tools': {
|
|
970
1026
|
const cols = Math.max(40, Math.min(process.stdout.columns || 90, 110));
|
|
971
1027
|
console.log('');
|
|
972
1028
|
for (const g of catalogo.GRUPOS) {
|
|
@@ -1055,6 +1111,29 @@ async function handleSlash(text) {
|
|
|
1055
1111
|
// Limpar aqui não apaga o que já subiu: encerra ESTA conversa e a
|
|
1056
1112
|
// próxima pergunta abre outra. Apagar do histórico é decisão da
|
|
1057
1113
|
// pessoa, e ela toma no app — não de raspão num comando de limpar tela.
|
|
1114
|
+
/* Resume a conversa por ordem da pessoa. Diferente do /clear: aqui
|
|
1115
|
+
nada é esquecido — o que já passou vira resumo e o fim continua
|
|
1116
|
+
cru, então dá para seguir de onde parou. */
|
|
1117
|
+
case 'compact': {
|
|
1118
|
+
if (!state.history.length) {
|
|
1119
|
+
console.log(c.muted(' A conversa está vazia — não há o que resumir.'));
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
const antes = memoria.usoDoContexto(state.memoria, state.history);
|
|
1123
|
+
await comprimirConversa(true);
|
|
1124
|
+
const depois = memoria.usoDoContexto(state.memoria, state.history);
|
|
1125
|
+
if (depois.tokens >= antes.tokens) {
|
|
1126
|
+
// Pode não haver o que dobrar (conversa curta, ou o servidor
|
|
1127
|
+
// recusou). Dizer isso é melhor que fingir que encolheu.
|
|
1128
|
+
console.log(c.muted(' Não deu para encolher mais desta vez.'));
|
|
1129
|
+
} else {
|
|
1130
|
+
console.log(c.ok(' Conversa resumida.')
|
|
1131
|
+
+ c.muted(` ctx ${antes.pct}% → ${depois.pct}%`));
|
|
1132
|
+
}
|
|
1133
|
+
refreshPrompt();
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1058
1137
|
case 'clear': state.history = []; state.memoria = memoria.nova();
|
|
1059
1138
|
state.gravador.zerar(); console.log(c.ok('Conversa limpa.')); break;
|
|
1060
1139
|
case 'cls': process.stdout.write('\x1Bc'); break;
|
|
@@ -1096,10 +1175,7 @@ async function handleSlash(text) {
|
|
|
1096
1175
|
não existe" e acha que a atualização quebrou alguma coisa; o aviso
|
|
1097
1176
|
custa uma linha e ensina o nome novo na hora em que ele é
|
|
1098
1177
|
procurado, que é a única hora em que alguém lê. */
|
|
1099
|
-
case 'rc':
|
|
1100
|
-
if (cmd.toLowerCase() === 'app') {
|
|
1101
|
-
console.log(c.muted(' Agora é ') + c.brand('/rc') + c.muted('. Abrindo…'));
|
|
1102
|
-
}
|
|
1178
|
+
case 'rc': {
|
|
1103
1179
|
// `/rc --instalar` põe o PrimoCode no Dock, no menu Iniciar ou no
|
|
1104
1180
|
// menu de aplicativos, com o ícone do mascote. Uma vez só.
|
|
1105
1181
|
if (/^--?(instalar|install)$/.test(rest)) {
|
|
@@ -1150,7 +1226,7 @@ async function handleSlash(text) {
|
|
|
1150
1226
|
// /rc viraria ruído para quem já instalou de propósito.
|
|
1151
1227
|
if (!require('../lib/instalar-app.js').instalado()) {
|
|
1152
1228
|
console.log(c.dim(' Quer o ícone no ') + c.dim(os.platform() === 'darwin' ? 'Dock' : 'menu Iniciar')
|
|
1153
|
-
+ c.dim('? ') + c.brand('/
|
|
1229
|
+
+ c.dim('? ') + c.brand('/rc --instalar'));
|
|
1154
1230
|
}
|
|
1155
1231
|
} catch (e) {
|
|
1156
1232
|
sp.stop();
|
|
@@ -1161,15 +1237,12 @@ async function handleSlash(text) {
|
|
|
1161
1237
|
|
|
1162
1238
|
/* A conta do Conecta Primo. O login inteiro mora em lib/conta.js;
|
|
1163
1239
|
aqui é a tela dele. */
|
|
1164
|
-
case '
|
|
1165
|
-
if (/^(entrar|login)$/.test(cmd.toLowerCase())) {
|
|
1166
|
-
console.log(c.muted(' Agora é ') + c.brand('/conectar') + c.muted('.'));
|
|
1167
|
-
}
|
|
1240
|
+
case 'connect': {
|
|
1168
1241
|
const jaEsta = conta.atual();
|
|
1169
1242
|
if (jaEsta && rest !== 'trocar') {
|
|
1170
1243
|
console.log(c.muted(' Já conectado como ') + c.brand(jaEsta.uid.slice(0, 12) + '…')
|
|
1171
1244
|
+ c.muted(` · plano ${jaEsta.plano}`));
|
|
1172
|
-
console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/
|
|
1245
|
+
console.log(c.dim(' Para entrar com outra conta: ') + c.brand('/connect trocar'));
|
|
1173
1246
|
break;
|
|
1174
1247
|
}
|
|
1175
1248
|
if (rest === 'trocar') conta.sair();
|
|
@@ -1185,11 +1258,20 @@ async function handleSlash(text) {
|
|
|
1185
1258
|
console.log(' ' + c.brand(msg));
|
|
1186
1259
|
primeiro = false;
|
|
1187
1260
|
} else {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1261
|
+
/* O código é para DIGITAR na página, e é ele que
|
|
1262
|
+
protege: como não vai no link, quem não está olhando
|
|
1263
|
+
este terminal não tem o que digitar — e um link
|
|
1264
|
+
mandado por outra pessoa não conecta o computador
|
|
1265
|
+
dela à sua conta.
|
|
1266
|
+
|
|
1267
|
+
Por isso ele aparece grande e sozinho, com o traço
|
|
1268
|
+
no meio: o que se lê de uma tela e se digita noutra
|
|
1269
|
+
erra menos partido em dois. */
|
|
1270
|
+
const meio = msg.length === 8 ? msg.slice(0, 4) + '-' + msg.slice(4) : msg;
|
|
1271
|
+
console.log('');
|
|
1272
|
+
console.log(c.muted(' Digite este código na página:'));
|
|
1273
|
+
console.log(' ' + c.bold(c.brand(meio.split('').join(' '))));
|
|
1274
|
+
console.log('');
|
|
1193
1275
|
console.log(c.dim(' Esperando você aprovar…'));
|
|
1194
1276
|
}
|
|
1195
1277
|
},
|
|
@@ -1203,9 +1285,9 @@ async function handleSlash(text) {
|
|
|
1203
1285
|
|
|
1204
1286
|
/* O histórico da conta — o MESMO que o app desktop e o celular
|
|
1205
1287
|
mostram. Sem número, lista; com número, abre e continua dali. */
|
|
1206
|
-
case '
|
|
1288
|
+
case 'chats': {
|
|
1207
1289
|
if (!conta.atual()) {
|
|
1208
|
-
console.log(c.muted(' Não conectado. ') + c.brand('/
|
|
1290
|
+
console.log(c.muted(' Não conectado. ') + c.brand('/connect')
|
|
1209
1291
|
+ c.muted(' para ver suas conversas aqui, no app e no celular.'));
|
|
1210
1292
|
break;
|
|
1211
1293
|
}
|
|
@@ -1226,7 +1308,7 @@ async function handleSlash(text) {
|
|
|
1226
1308
|
+ ' ' + c.white(x.titulo.slice(0, 52).padEnd(52))
|
|
1227
1309
|
+ c.dim(`${String(x.mensagens).padStart(3)} msg ${quando}`));
|
|
1228
1310
|
});
|
|
1229
|
-
console.log(c.dim('\n /
|
|
1311
|
+
console.log(c.dim('\n /chats 1 abre a primeira e continua dela.'));
|
|
1230
1312
|
break;
|
|
1231
1313
|
}
|
|
1232
1314
|
const n = Number(rest);
|
|
@@ -1250,18 +1332,18 @@ async function handleSlash(text) {
|
|
|
1250
1332
|
break;
|
|
1251
1333
|
}
|
|
1252
1334
|
|
|
1253
|
-
case '
|
|
1335
|
+
case 'logout': {
|
|
1254
1336
|
if (!conta.atual()) { console.log(c.muted(' Você não está conectado.')); break; }
|
|
1255
1337
|
conta.sair();
|
|
1256
1338
|
console.log(c.ok(' Desconectado deste computador.')
|
|
1257
|
-
+ c.dim(' A conta continua intacta; entre de novo com /
|
|
1339
|
+
+ c.dim(' A conta continua intacta; entre de novo com /connect.'));
|
|
1258
1340
|
break;
|
|
1259
1341
|
}
|
|
1260
1342
|
|
|
1261
|
-
case '
|
|
1343
|
+
case 'account': {
|
|
1262
1344
|
const eu = conta.atual();
|
|
1263
1345
|
if (!eu) {
|
|
1264
|
-
console.log(c.muted(' Não conectado. ') + c.brand('/
|
|
1346
|
+
console.log(c.muted(' Não conectado. ') + c.brand('/connect') + c.muted(' para entrar.'));
|
|
1265
1347
|
break;
|
|
1266
1348
|
}
|
|
1267
1349
|
const sp = new Spinner('conferindo').start();
|
|
@@ -1582,7 +1664,7 @@ async function main() {
|
|
|
1582
1664
|
cores: c,
|
|
1583
1665
|
interativo: !!(process.stdin.isTTY && process.stdout.isTTY),
|
|
1584
1666
|
conectado: () => conta.atual(),
|
|
1585
|
-
conectar: () => handleSlash('/
|
|
1667
|
+
conectar: () => handleSlash('/connect').then(() => conta.atual()),
|
|
1586
1668
|
sessao: () => conta.sessao(),
|
|
1587
1669
|
aplicar: (cmd) => handleSlash(cmd),
|
|
1588
1670
|
}).then((r) => {
|
package/lib/act.js
CHANGED
|
@@ -616,6 +616,24 @@ async function runAct(userPrompt, opts) {
|
|
|
616
616
|
continue;
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
+
/* O PIPELINE DE QUATRO PAPÉIS mora aqui pelo mesmo motivo que o
|
|
620
|
+
`spawn_agent`: ele PRECISA rodar agentes, e quem sabe rodar
|
|
621
|
+
agente é este arquivo. Pôr isto no `tools.execute` obrigaria
|
|
622
|
+
aquele módulo a conhecer o loop do agente — e um segundo lugar
|
|
623
|
+
que sabe abrir conversa com o modelo é um segundo lugar para
|
|
624
|
+
esquecer de passar o token, a pasta ou o motor. */
|
|
625
|
+
if (tc.name === 'construir_app') {
|
|
626
|
+
algoRodou = true;
|
|
627
|
+
console.log(ident(profundidade) + actionHeader(tc.name, tc.arguments));
|
|
628
|
+
const r = await rodarPipeline(tc.arguments || {}, {
|
|
629
|
+
server, model, token, projectDir: pastaAtual, projectName, profundidade, motor,
|
|
630
|
+
});
|
|
631
|
+
logResult(tc.name, r, profundidade);
|
|
632
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(r) });
|
|
633
|
+
if (r && r.ok && catalogo.escreveArquivo(tc.name)) temPasta = true;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
|
|
619
637
|
// Repetiu a mesma chamada? Não executa de novo. Rodar outra vez
|
|
620
638
|
// gastaria a cota para devolver exatamente o mesmo texto, e é
|
|
621
639
|
// esse texto que convence o modelo a repetir mais uma vez.
|
|
@@ -798,5 +816,55 @@ async function runSubagent(args, ctx) {
|
|
|
798
816
|
return resumo;
|
|
799
817
|
}
|
|
800
818
|
|
|
819
|
+
/* ══ O PIPELINE DE QUATRO PAPÉIS ═══════════════════════════════════════════
|
|
820
|
+
*
|
|
821
|
+
* "Engenheiro de Prompt → Construtor 1 (protótipo) → Construtor 2 (MVP) →
|
|
822
|
+
* Revisador (testa ocultamente) → loop de correção → abre no navegador."
|
|
823
|
+
*
|
|
824
|
+
* As regras dos papéis, os prompts e o revisor que abre a página moram em
|
|
825
|
+
* `pipeline.js`, que não conhece o loop do agente. O que esta função faz é a
|
|
826
|
+
* costura: dar a ele uma forma de RODAR um agente, uma de escrever na tela e
|
|
827
|
+
* uma de abrir o navegador da pessoa.
|
|
828
|
+
*
|
|
829
|
+
* Cada papel roda com `history: []` — conversa própria, sem ver a do usuário
|
|
830
|
+
* nem a do papel anterior. É essa separação que faz o revisor valer alguma
|
|
831
|
+
* coisa: quem acabou de escrever o código é o pior revisor dele, porque lê o
|
|
832
|
+
* que quis escrever em vez do que escreveu.
|
|
833
|
+
*/
|
|
834
|
+
async function rodarPipeline(args, ctx) {
|
|
835
|
+
const { server, model, token, projectDir, projectName, profundidade, motor } = ctx;
|
|
836
|
+
if (profundidade > 0) {
|
|
837
|
+
return { ok: false, error: 'Recusado: o pipeline não roda dentro de um subagente — '
|
|
838
|
+
+ 'os papéis dele já são subagentes. Faça a parte que te cabe.' };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const pipeline = require('./pipeline.js');
|
|
842
|
+
const path = require('path');
|
|
843
|
+
const pedido = args.pedido || args.prompt || '';
|
|
844
|
+
const base = projectDir || cfg.ROOT_DIR;
|
|
845
|
+
const pasta = args.pasta
|
|
846
|
+
? path.resolve(base, args.pasta)
|
|
847
|
+
: pipeline.pastaPara(pedido, base);
|
|
848
|
+
|
|
849
|
+
const correr = async (prompt, rotulo) => {
|
|
850
|
+
if (PARAR) return '';
|
|
851
|
+
console.log(ident(profundidade) + ` ${c.magenta('◆')} ${c.bold(c.magenta('Agente'))} ${c.muted(rotulo)}`);
|
|
852
|
+
const r = await runAct(prompt, {
|
|
853
|
+
server, model, token, projectDir: pasta, projectName,
|
|
854
|
+
history: [], profundidade: profundidade + 1, subagent: true, motor,
|
|
855
|
+
});
|
|
856
|
+
return (r && r.assistantMessage && r.assistantMessage.content) || '';
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
return pipeline.construir({
|
|
860
|
+
pedido,
|
|
861
|
+
pasta,
|
|
862
|
+
rodadas: args.rodadas,
|
|
863
|
+
correr,
|
|
864
|
+
mostrar: (linha) => console.log(ident(profundidade) + ` ${c.muted('⎿ ' + linha)}`),
|
|
865
|
+
abrir: tools.openInBrowser,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
801
869
|
module.exports = { runAct, interromper, compactar, motivoDaParada, ehTetoDeToken, esperaDoErro,
|
|
802
870
|
anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
|