primocode 11.4.0 → 11.5.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.
Files changed (85) hide show
  1. package/LICENSES/edit2-terceiros.md +31 -0
  2. package/LICENSES/models-dev-MIT.txt +25 -0
  3. package/README.md +2 -2
  4. package/editor/SKILL.md +24 -0
  5. package/editor/THIRD_PARTY_NOTICES.md +1 -0
  6. package/editor/assets/baixar.json +9 -1
  7. package/editor/helpers/df_limpar.py +29 -0
  8. package/editor/helpers/edit2.py +155 -0
  9. package/editor/helpers/enquadrar_camera.py +77 -0
  10. package/editor/helpers/face_layout.py +20 -0
  11. package/editor/helpers/face_mediapipe.py +147 -0
  12. package/editor/helpers/instalar_df.sh +7 -0
  13. package/editor/helpers/legenda_ass.py +124 -0
  14. package/editor/helpers/look_ffmpeg.py +105 -0
  15. package/editor/helpers/motor_palavras.py +157 -0
  16. package/editor/helpers/primeiro_passe.py +214 -0
  17. package/editor/helpers/som_edit2.py +119 -0
  18. package/editor/helpers/transcribe.py +31 -2
  19. package/editor/pyproject.toml +9 -0
  20. package/editor/references/edit2.md +22 -0
  21. package/editor/uv.lock +3342 -214
  22. package/kits/3d/LICENSES/README.md +11 -0
  23. package/kits/3d/animacoes.js +87 -0
  24. package/kits/3d/base.js +44 -0
  25. package/kits/3d/catalogo.js +38 -0
  26. package/kits/3d/cenario.js +162 -0
  27. package/kits/3d/exemplos/asfalto.mjs +91 -0
  28. package/kits/3d/maquinas.js +97 -0
  29. package/kits/3d/montador.js +142 -0
  30. package/kits/3d/package.json +1 -0
  31. package/kits/3d/personagem.js +86 -0
  32. package/kits/3d/remotion/Maquete.jsx +42 -0
  33. package/kits/3d/remotion/Root.jsx +10 -0
  34. package/kits/3d/render.mjs +73 -0
  35. package/lib/cli/comando-api.js +30 -8
  36. package/lib/cli/modelos-lista.js +58 -0
  37. package/lib/cli/sessao.js +11 -0
  38. package/lib/cli/subcomandos.js +3 -2
  39. package/lib/outra-pasta.js +1 -1
  40. package/lib/permissao-sistema.js +60 -0
  41. package/lib/permission/perigo.js +1 -0
  42. package/lib/permission/permissao.js +1 -0
  43. package/lib/permission/sem-perguntar.js +29 -0
  44. package/lib/plugin/desktop.js +5 -4
  45. package/lib/plugin/estudio/cota.js +4 -2
  46. package/lib/plugin/estudio/dialogo3d.js +71 -0
  47. package/lib/plugin/estudio/effectcraft.js +1 -1
  48. package/lib/plugin/estudio/glifos.js +2 -0
  49. package/lib/plugin/estudio/gsap-local.js +43 -0
  50. package/lib/plugin/estudio/index.js +63 -10
  51. package/lib/plugin/estudio/kit-ae/base.jsx +1 -1
  52. package/lib/plugin/estudio/kit-ae/cenas.jsx +7 -4
  53. package/lib/plugin/estudio/kit-ae/destrava.jsx +211 -0
  54. package/lib/plugin/estudio/maquina-diretor.js +1 -1
  55. package/lib/plugin/estudio/maquina-elementos.js +2 -2
  56. package/lib/plugin/estudio/maquina.js +26 -8
  57. package/lib/plugin/estudio/motion-plano/ajustar.js +139 -0
  58. package/lib/plugin/estudio/motion-plano/critico.js +59 -0
  59. package/lib/plugin/estudio/motion-plano/estrutura.js +111 -0
  60. package/lib/plugin/estudio/motion-plano/fatos.js +137 -0
  61. package/lib/plugin/estudio/motion-plano/index.js +75 -0
  62. package/lib/plugin/estudio/motion-plano/llm.js +59 -0
  63. package/lib/plugin/estudio/motion-plano/motor.js +50 -0
  64. package/lib/plugin/estudio/motion-plano/passos.js +38 -0
  65. package/lib/plugin/estudio/motion-plano/pele.js +36 -0
  66. package/lib/plugin/estudio/motion-plano/plano.js +152 -0
  67. package/lib/plugin/estudio/motion-plano/regua.js +100 -0
  68. package/lib/plugin/estudio/personagem.js +3 -1
  69. package/lib/plugin/estudio/referencias.js +1 -1
  70. package/lib/provider/catalogo.js +161 -0
  71. package/lib/provider/local/catalogo-embutido.json.gz +0 -0
  72. package/lib/provider/local/motor.js +140 -27
  73. package/lib/provider/local/orcamento.js +193 -0
  74. package/lib/provider/local/provedor.js +127 -14
  75. package/lib/provider/local/recorte.js +170 -0
  76. package/lib/provider/local/revezamento.js +121 -0
  77. package/lib/session/runner/act.js +28 -3
  78. package/lib/skill/catalogo.js +5 -1
  79. package/lib/tool/tools.js +5 -1
  80. package/package.json +1 -1
  81. package/vendor/fontes/ttf/ORIGEM.txt +1 -0
  82. package/vendor/fontes/ttf/inter-800.ttf +0 -0
  83. package/vendor/gsap/ORIGEM.txt +6 -0
  84. package/vendor/gsap/gsap.min.js +11 -0
  85. package/voz/primo_voz/chat.html +167 -5
@@ -0,0 +1,170 @@
1
+ /**
2
+ * recorte.js — quais ferramentas o pedido precisa, por assunto.
3
+ *
4
+ * Reaproveita os gatilhos de entrada.js (os mesmos do servidor) e completa o
5
+ * que o cliente tem a mais: cada ferramenta cai num assunto, e o que nenhum
6
+ * gatilho pegou continua a UM comando de distância, pela ferramenta pequena
7
+ * `mais_ferramentas(grupo)`.
8
+ *
9
+ * Sempre vai o núcleo (arquivos, terminal, to-dos, finish). O modo atual pode
10
+ * forçar assuntos: o Estúdio de motion só precisa de motion + arquivos.
11
+ *
12
+ * Sem estado: o que o modelo ligou antes aparece na própria conversa (a
13
+ * chamada a `mais_ferramentas`, ou o uso de uma ferramenta de outro assunto),
14
+ * e é lido de lá a cada volta.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const entrada = require('./entrada.js');
20
+
21
+ /** Ferramentas do cliente que os grupos de entrada.js não conhecem. */
22
+ const EXTRAS = { estudio: ['estudio_criar'], 'after-effects': ['estudio_criar'], gravacao: ['editor_pro'] };
23
+ const NOVOS = [
24
+ { nome: 'web', tools: ['buscar_web', 'ler_url'],
25
+ gatilho: /\b(https?:|www\.|link|pesquis\w*|busca\w*|internet|not[íi]cia|site de|documenta[çc][ãa]o)\b/i },
26
+ { nome: 'memoria', tools: ['lembrar', 'remember_general'],
27
+ gatilho: /\b(lembr\w*|mem[óo]ria|guarda (isso|que)|anota (isso|que)|n[ãa]o esque[çc]a)\b/i },
28
+ { nome: 'ajuda', tools: ['perguntar', 'agentes_buscar', 'capacidade_buscar'], gatilho: null },
29
+ ];
30
+
31
+ /** Os nomes que o modelo vê. 'after-effects' aparece como 'motion'. */
32
+ const ALIAS = { motion: 'after-effects', 'after-effects': 'after-effects' };
33
+
34
+ const RESUMO = {
35
+ navegador: 'controlar um navegador (abrir, clicar, ler, fotografar)',
36
+ computador: 'controlar o computador (janelas, mouse, teclado, tela)',
37
+ mcp: 'conectar serviços (GitHub, Notion, Slack, banco...)',
38
+ pastas: 'trabalhar em outra pasta',
39
+ pesquisa: 'pesquisar documentação e referências',
40
+ pipeline: 'construir um app ou site inteiro',
41
+ exportar: 'renderizar o vídeo em arquivo',
42
+ delegar: 'chamar subagentes',
43
+ terminal: 'servidor ou watch em segundo plano',
44
+ estudio: 'estúdio: slides, vídeo e arte',
45
+ gravacao: 'gravação do usuário: cortar, legendar, editar',
46
+ motion: 'motion: After Effects, estudio_motion, editar o vídeo',
47
+ web: 'buscar na web e ler links',
48
+ memoria: 'guardar memória entre sessões',
49
+ ajuda: 'perguntar ao usuário, achar agente ou receita',
50
+ };
51
+
52
+ function tabela() {
53
+ const mapa = new Map(); // nome interno → ferramentas
54
+ for (const g of entrada.GRUPOS) {
55
+ if (!g.nome) continue;
56
+ mapa.set(g.nome, [...g.tools, ...(EXTRAS[g.nome] || [])]);
57
+ }
58
+ for (const n of NOVOS) mapa.set(n.nome, n.tools);
59
+ return mapa;
60
+ }
61
+
62
+ /** O que o modo atual força. Estúdio de motion: só motion (os arquivos já são o núcleo). */
63
+ function forcarDoModo(modo) {
64
+ return /^(estudio|motion)$/i.test(String(modo || '')) ? ['after-effects'] : [];
65
+ }
66
+
67
+ function chamadasDaConversa(mensagens) {
68
+ const lista = [];
69
+ for (const m of mensagens || []) {
70
+ for (const tc of (m && m.tool_calls) || []) {
71
+ lista.push({ nome: (tc.function && tc.function.name) || tc.name, args: (tc.function && tc.function.arguments) || tc.arguments });
72
+ }
73
+ }
74
+ return lista;
75
+ }
76
+
77
+ /**
78
+ * Os assuntos deste pedido. `usados` = o que a conversa já ligou: o grupo que
79
+ * o modelo pediu em `mais_ferramentas` e o de qualquer ferramenta que ele
80
+ * chamou (se chamou, vai querer chamar de novo).
81
+ */
82
+ function gruposDoPedido(texto, mensagens = [], forcar = []) {
83
+ const grupos = new Set(entrada.gruposDe(texto, forcar));
84
+ for (const n of NOVOS) if (n.gatilho && n.gatilho.test(String(texto || ''))) grupos.add(n.nome);
85
+ const usados = new Set();
86
+ const t = tabela();
87
+ for (const c of chamadasDaConversa(mensagens)) {
88
+ if (c.nome === 'mais_ferramentas') {
89
+ let a = c.args;
90
+ if (typeof a === 'string') { try { a = JSON.parse(a); } catch { a = {}; } }
91
+ const g = ALIAS[String(a && a.grupo || '').toLowerCase()] || String(a && a.grupo || '').toLowerCase();
92
+ if (t.has(g)) usados.add(g);
93
+ continue;
94
+ }
95
+ for (const [g, tools] of t) if (tools.includes(c.nome) && g !== 'nucleo') usados.add(g);
96
+ }
97
+ for (const g of usados) grupos.add(g);
98
+ return { grupos, usados };
99
+ }
100
+
101
+ /** As ferramentas dos grupos, na ordem original; o núcleo sempre. */
102
+ function escolher(todas, grupos) {
103
+ const t = tabela();
104
+ const permitidas = new Set();
105
+ for (const [g, tools] of t) if (g === 'nucleo' || grupos.has(g)) tools.forEach((x) => permitidas.add(x));
106
+ permitidas.add('finish');
107
+ const lista = (todas || []).filter((x) => permitidas.has(x.function.name));
108
+ return lista.length >= 5 ? lista : todas; // rede de segurança, como em entrada.js
109
+ }
110
+
111
+ /** A ferramenta pequena que liga um grupo na próxima volta. Só lista o que ficou de fora. */
112
+ function maisFerramentas(todas, grupos) {
113
+ const t = tabela();
114
+ const nomes = new Set((todas || []).map((x) => x.function.name));
115
+ const fora = [];
116
+ for (const [g, tools] of t) {
117
+ if (g === 'nucleo' || grupos.has(g) || !tools.some((x) => nomes.has(x))) continue;
118
+ fora.push(g === 'after-effects' ? 'motion' : g);
119
+ }
120
+ return { type: 'function', function: {
121
+ name: 'mais_ferramentas',
122
+ description: `Liga um grupo de ferramentas na próxima volta. Grupos: ${fora.map((g) => `${g} (${RESUMO[g] || g})`).join('; ')}.`,
123
+ parameters: { type: 'object', properties: { grupo: { type: 'string', enum: fora } }, required: ['grupo'] },
124
+ } };
125
+ }
126
+
127
+ /** Sem caber nem no nível mais curto: o assunto secundário sai, o principal fica (motion manda sobre estúdio). */
128
+ function principais(grupos) {
129
+ if (grupos.has('after-effects')) { const g = new Set(grupos); g.delete('estudio'); g.delete('gravacao'); return g; }
130
+ return grupos;
131
+ }
132
+
133
+ /* ── O PEDIDO DE VERDADE ──────────────────────────────────────────────────
134
+ * O CLI embrulha o que a pessoa escreveu: na frente vão o menu de estilos, a tabela de comandos e a lista de
135
+ * skills ("PEDIDO: <o que ela escreveu>"), e atrás, o aviso de escopo. Casar gatilho contra o embrulho inteiro
136
+ * liga todos os assuntos (a tabela cita desktop, navegador, motion...) e o recorte não recorta nada. */
137
+ function pedidoReal(texto) {
138
+ let s = String(texto || '');
139
+ const i = s.lastIndexOf('PEDIDO:');
140
+ if (i >= 0) s = s.slice(i + 7);
141
+ const j = s.lastIndexOf('RESPOSTA DA PESSOA:');
142
+ if (j >= 0) s = s.slice(j + 19);
143
+ return s.split(/\n\n(?:ESCOPO:|\[)/)[0].trim();
144
+ }
145
+
146
+ /** Teto, em caracteres, da memória persistente em cada nível apertado. */
147
+ const TETO_MEMORIA = { 3: 2500, 4: 1200, 5: 1200 };
148
+
149
+ /**
150
+ * O contexto que o CLI cola na conversa, encolhido nos níveis fundos: a frente do último pedido
151
+ * (tudo antes de "PEDIDO:") sai, e a memória persistente é cortada. O pedido em si nunca é tocado.
152
+ */
153
+ function enxugarContexto(mensagens, nivel) {
154
+ if (nivel < 3) return mensagens;
155
+ const ultimoUser = (() => { for (let i = mensagens.length - 1; i >= 0; i--) if (mensagens[i] && mensagens[i].role === 'user') return i; return -1; })();
156
+ return mensagens.map((m, i) => {
157
+ if (!m || typeof m.content !== 'string') return m;
158
+ if (i === ultimoUser) {
159
+ const k = m.content.lastIndexOf('PEDIDO:');
160
+ return k > 0 ? { ...m, content: m.content.slice(k + 7).trim() } : m;
161
+ }
162
+ if (m.role === 'system' && /^Memória persistente:/.test(m.content)) {
163
+ const max = TETO_MEMORIA[nivel] || 1200;
164
+ return m.content.length > max ? { ...m, content: `${m.content.slice(0, max)}\n…(memória cortada: o provedor grátis limita os tokens por minuto)` } : m;
165
+ }
166
+ return m;
167
+ });
168
+ }
169
+
170
+ module.exports = { pedidoReal, enxugarContexto, principais, RESUMO, forcarDoModo, gruposDoPedido, escolher, maisFerramentas, tabela };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * revezamento.js — revezar provedores grátis em vez de esperar o limite por minuto.
3
+ *
4
+ * O plano grátis do Groq para 6–37 s a cada 429/413. Se a pessoa tem a chave de
5
+ * outro provedor grátis no ambiente, o pedido segue nele; só espera se nenhum estiver livre.
6
+ *
7
+ * ordem padrão: groq → cerebras → gemini (endpoint compatível com OpenAI do Google)
8
+ * entra só quem tem chave no ambiente (GROQ_API_KEY, CEREBRAS_API_KEY, GEMINI_API_KEY)
9
+ * e um modelo com ferramentas no catálogo (lib/provider/catalogo.js)
10
+ *
11
+ * Config da pessoa, opcional: ~/.primocode/revezamento.json
12
+ * { "ativo": true, "ordem": ["groq","cerebras","gemini"], "modelos": { "cerebras": "gpt-oss-120b" } }
13
+ * "ativo": false (ou PRIMOCODE_SEM_REVEZAMENTO=1) desliga: volta a esperar, como antes.
14
+ *
15
+ * Nenhuma chave nem URL privada mora aqui: as bases vêm do catálogo/provedor.js.
16
+ * O provedor atual só revezam se ele mesmo está na ordem (quem usa gateway pago não é tocado).
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const os = require('os');
23
+ const path = require('path');
24
+ const provedor = require('./provedor.js');
25
+ const catalogo = require('../catalogo');
26
+
27
+ const ORDEM_PADRAO = ['groq', 'cerebras', 'gemini'];
28
+ const ARQUIVO = () => path.join(os.homedir(), '.primocode', 'revezamento.json');
29
+ const PAUSA_PADRAO_MS = 30000;
30
+ const PAUSA_DIA_MS = 6 * 60 * 60 * 1000;
31
+
32
+ const OCUPADOS = new Map(); // id do provedor → até quando está em pausa (ms)
33
+
34
+ function config() {
35
+ let j = {};
36
+ try { j = JSON.parse(fs.readFileSync(ARQUIVO(), 'utf8')) || {}; } catch { /* sem arquivo: padrão */ }
37
+ const ordem = Array.isArray(j.ordem) ? j.ordem.map(String).filter(Boolean) : ORDEM_PADRAO;
38
+ return {
39
+ ativo: j.ativo !== false && process.env.PRIMOCODE_SEM_REVEZAMENTO !== '1',
40
+ ordem: ordem.length ? ordem : ORDEM_PADRAO,
41
+ modelos: j.modelos && typeof j.modelos === 'object' ? j.modelos : {},
42
+ };
43
+ }
44
+
45
+ function capDe(p, modelo) {
46
+ const k = p.noCatalogo || p.id;
47
+ return catalogo.capacidades(k, modelo);
48
+ }
49
+
50
+ /** O melhor modelo do provedor `p` (resolvido): com ferramentas e, se pedido, com visão. null se não há. */
51
+ function modeloDe(p, { visao = false, preferido = '' } = {}) {
52
+ const lista = [preferido, ...(p.modelos || [])].filter(Boolean);
53
+ const k = p.noCatalogo || p.id;
54
+ const doCat = catalogo.carregarSync().provedores[k];
55
+ if (doCat) for (const m of Object.keys(doCat.modelos)) lista.push(m);
56
+ for (const m of lista) {
57
+ const c = capDe(p, m);
58
+ if (!c) continue; // fora do catálogo: não dá para garantir ferramentas
59
+ if (c.ferramentas && (!visao || c.visao)) return m;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ /** A pausa vale até quando? `esperaMs` vem do header do provedor. */
65
+ function marcarOcupado(id, esperaMs, { dia = false } = {}) {
66
+ OCUPADOS.set(id, Date.now() + (dia ? PAUSA_DIA_MS : (esperaMs && esperaMs > 0 ? esperaMs + 500 : PAUSA_PADRAO_MS)));
67
+ }
68
+ function livre(id) {
69
+ const ate = OCUPADOS.get(id);
70
+ if (!ate) return true;
71
+ if (ate <= Date.now()) { OCUPADOS.delete(id); return true; }
72
+ return false;
73
+ }
74
+ function esquecer() { OCUPADOS.clear(); }
75
+
76
+ /** Os provedores do revezamento, na ordem, prontos para uso. `atual` é o que a pessoa escolheu. */
77
+ function candidatos(atual, { visao = false } = {}) {
78
+ const cfg = config();
79
+ if (!cfg.ativo || !atual || !cfg.ordem.includes(atual.id)) return [];
80
+ const saida = [];
81
+ for (const id of cfg.ordem) {
82
+ let p;
83
+ if (id === atual.id) {
84
+ p = { ...atual };
85
+ const resolvido = provedor.resolver(id) || {};
86
+ p.modelos = [atual.modelo, ...(resolvido.modelos || [])];
87
+ p.noCatalogo = atual.noCatalogo || resolvido.noCatalogo || (resolvido.catalogo ? id : '');
88
+ } else {
89
+ const r = provedor.resolver(id);
90
+ const chave = r && provedor.chaveDoAmbiente(r);
91
+ if (!r || r.cli || !chave) continue;
92
+ p = { id, nome: r.nome, base: r.base, chave, modelos: r.modelos || [], noCatalogo: r.noCatalogo || (r.catalogo ? id : '') };
93
+ }
94
+ const modelo = modeloDe(p, { visao, preferido: id === atual.id ? atual.modelo : (cfg.modelos[id] || '') });
95
+ if (!modelo) continue;
96
+ saida.push({ id: p.id, nome: p.nome, cli: false, base: p.base, modelo, chave: p.chave, daAmbiente: p.daAmbiente, noCatalogo: p.noCatalogo });
97
+ }
98
+ return saida;
99
+ }
100
+
101
+ /**
102
+ * Com qual provedor o pedido sai agora? Devolve { p, trocou, de } — `p` é o próprio `atual`
103
+ * quando nada muda. Pula quem está em pausa; com imagem, prefere quem enxerga.
104
+ */
105
+ function escolher(atual, { visao = false, evitar = [] } = {}) {
106
+ const livres = candidatos(atual, { visao }).filter((c) => livre(c.id) && !evitar.includes(c.id));
107
+ if (!livres.length) return { p: atual, trocou: false, de: atual };
108
+ // o provedor da pessoa tem preferência enquanto estiver livre (e, com imagem, se enxergar)
109
+ const proprio = livres.find((c) => c.id === atual.id);
110
+ const p = proprio || livres[0];
111
+ if (!visao && proprio) return { p: atual, trocou: false, de: atual };
112
+ return { p, trocou: p.id !== atual.id || p.modelo !== atual.modelo, de: atual };
113
+ }
114
+
115
+ /** A frase para a pessoa. */
116
+ function frase(de, para, motivo = 'pausa') {
117
+ if (motivo === 'visao') return `${para.nome} enxerga imagens; segui nele.`;
118
+ return `O ${de.nome} pediu pausa; segui no ${para.nome}.`;
119
+ }
120
+
121
+ module.exports = { ORDEM_PADRAO, ARQUIVO, config, candidatos, escolher, frase, marcarOcupado, livre, esquecer, modeloDe };
@@ -29,6 +29,7 @@ const api = require('../../provider/api');
29
29
  const cfg = require('../../config/config');
30
30
  const catalogo = require('../../skill/catalogo');
31
31
  const repeticao = require('../../repeticao');
32
+ const permissaoSistema = require('../../permissao-sistema');
32
33
  const diff = require('../../diff');
33
34
  const registro = require('../../registro');
34
35
  const permissao = require('../../permission/permissao');
@@ -1146,6 +1147,8 @@ async function runActInterno(userPrompt, opts) {
1146
1147
  mesmo arquivo por uma hora). Empurra uma vez; na segunda, para. */
1147
1148
  const circulo = travado.novo();
1148
1149
  let paradoPorTravar = null;
1150
+ /* Falta permissão do macOS (lib/permissao-sistema.js): para na PRIMEIRA falha, sem tentar de novo. */
1151
+ let paradoPorPermissao = null;
1149
1152
  /* Anota ANTES de agir (lib/auditoria.js): recusado também fica no livro. */
1150
1153
  const anotar = (tc, decisao, motivo) => {
1151
1154
  if (decisao !== 'recusada' && decisao !== 'negada') require('../../plugin/voz-ponte').ferramenta(tc.name, tc.arguments);
@@ -1435,7 +1438,8 @@ async function runActInterno(userPrompt, opts) {
1435
1438
  }
1436
1439
  const enviadas = compactar(messages, { folgado });
1437
1440
  traco(`volta ${voltas}${subagent ? ' (agente)' : ''} · ${enviadas.length} mensagens · ${JSON.stringify(enviadas).length} caracteres`);
1438
- await motor.requestAct(server, { messages: enviadas, model, preferido: preferido || undefined, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
1441
+ await motor.requestAct(server, { messages: enviadas, model, preferido: preferido || undefined, effort, token, projectDir: pastaAtual, projectName, subagent, modo: opts.modo, sinal: emVoo.signal }, (evt) => {
1442
+ if (evt.aviso) { if (!subagent) ev('linha', { texto: ` ⎿ ${evt.aviso}`, nivel: 'aviso' }); return; }
1439
1443
  if (evt.erro) { streamError = folgado ? evt.erro : erroDaCorrente(evt.erro); return; }
1440
1444
  if (evt.modelo) { modeloDaVolta = evt; ultimoModelo = evt.modelo; if (sinais) sinais.modelo = evt.modelo; require('../../cli/statusline').registrarModelo(evt.modelo); return; }
1441
1445
  if (evt.tool_call) {
@@ -1791,6 +1795,12 @@ async function runActInterno(userPrompt, opts) {
1791
1795
 
1792
1796
  let motivoTravou = null;
1793
1797
  for (const tc of toolCalls) {
1798
+ // A permissão já faltou nesta volta: o resto do lote não roda, mas cada chamada precisa de resposta.
1799
+ if (paradoPorPermissao) {
1800
+ anotar(tc, 'recusada', 'falta permissão do macOS');
1801
+ messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false, error: permissaoSistema.mensagem(paradoPorPermissao) }) });
1802
+ continue;
1803
+ }
1794
1804
  /* O JSON da chamada veio quebrado (lib/chamada-torta.js): a
1795
1805
  ferramenta não roda, e o modelo recebe o erro com os argumentos
1796
1806
  crus para se corrigir — em vez de rodar com `{}` e repetir. */
@@ -2113,10 +2123,19 @@ async function runActInterno(userPrompt, opts) {
2113
2123
  respondia "a transcrição precisa do servidor". */
2114
2124
  traco(`executando ${tc.name}`);
2115
2125
  const idAcao = idsAdiantados.get(tc.id) || anotar(tc, 'permitida');
2116
- const result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
2126
+ let result = adiantadas.has(tc.id) ? adiantadas.get(tc.id)
2117
2127
  : await tools.execute(tc.name, tc.arguments, { projectDir: pastaAtual, server, token, api, escolher: subagent ? undefined : escolher,
2118
2128
  // estudio_criar conta o andamento: a tela desenha a barra do item `estudio`.
2119
- progresso: tc.name === 'estudio_criar' ? (pct, etapa) => ev('estudio-progresso', { pct, etapa }) : undefined });
2129
+ progresso: tc.name === 'estudio_criar' ? (pct, etapa) => ev('estudio-progresso', { pct, etapa }) : undefined,
2130
+ // estudio_motion: o "Plano atualizado" das etapas (ajustar, estruturar, planejar) chega como linha na conversa.
2131
+ linha: tc.name === 'estudio_motion' ? (texto) => ev('linha', { texto }) : undefined });
2132
+ const faltaPermissao = permissaoSistema.detectar(tc.name, result);
2133
+ if (faltaPermissao) {
2134
+ result = permissaoSistema.comoErro(faltaPermissao, result);
2135
+ paradoPorPermissao = faltaPermissao;
2136
+ // no app desktop, esta linha no stderr abre a tela de permissões
2137
+ if (process.env.PRIMOCODE_APP === '1' && !subagent) { try { process.stderr.write(permissaoSistema.linhaProtocolo(faltaPermissao)); } catch { /* sem stderr */ } }
2138
+ }
2120
2139
  auditoria.depois(idAcao, result);
2121
2140
  if (sinais && result && result.ok && /^(write_file|edit_file|multi_edit)$/.test(tc.name)) sinais.diagSujo = !!result.diagnosticos; // diagnóstico depois da edição
2122
2141
  if (antesDaEdicao != null && result && result.ok !== false) {
@@ -2225,6 +2244,8 @@ async function runActInterno(userPrompt, opts) {
2225
2244
 
2226
2245
  /* ANDANDO EM CÍRCULO (lib/travado.js): a primeira vez, um empurrão
2227
2246
  com o que ele está repetindo; a segunda, para — antes da cota. */
2247
+ if (paradoPorPermissao) break; // a primeira falha de permissão encerra: repetir não adianta
2248
+
2228
2249
  const giro = travado.fimDaVolta(circulo);
2229
2250
  const travou = finished ? null : travado.decidir(circulo, motivoTravou || giro);
2230
2251
  if (travou && travou.acao === 'empurrar') {
@@ -2275,6 +2296,10 @@ async function runActInterno(userPrompt, opts) {
2275
2296
  const concluiu = Boolean(finalText) && !PARAR;
2276
2297
 
2277
2298
  let passagemDaParada = null;
2299
+ if (paradoPorPermissao && !finalText) {
2300
+ finalText = permissaoSistema.mensagem(paradoPorPermissao);
2301
+ if (!subagent) ev('aviso', { texto: finalText });
2302
+ }
2278
2303
  if (paradoPorTravar && !finalText) {
2279
2304
  finalText = paradoPorTravar;
2280
2305
  if (!subagent) ev('aviso', { texto: finalText });
@@ -151,7 +151,7 @@ const GRUPOS = [
151
151
  args: 'id. script (o JSX: app.project.items.addComp, camadas, setValueAtTime com ease). comp (o nome da composição). fontes [{familia, peso}]. '
152
152
  + 'audio (caminho relativo, juntado ao vídeo). conferir [segundos]: quadros PNG para olhar antes de dar por pronto. Leia a skill after-effects. Sai em video-ae.mp4.' },
153
153
  { tool: 'estudio_motion', escreve: true, faz: 'a máquina de motion: do assunto ao vídeo de motion pronto (cenas no After Effects, sons no tempo, trilha e voz opcionais, −14 LUFS)',
154
- args: 'id. assunto. formato (9:16|1:1|16:9). duracao (6 a 60 s). receita (id de estudio_midia acao:"projetos": usa o formato e a duração da ficha). '
154
+ args: 'id. assunto (ou pedido: as palavras do usuário; sem cenas, passa por 3 etapas antes do render: ajustar o pedido, estruturar em batidas e montar o plano cena por cena; etapas:false volta ao caminho antigo). formato (9:16|1:1|16:9). duracao (6 a 60 s). receita (id de estudio_midia acao:"projetos": usa o formato e a duração da ficha). '
155
155
  + 'marca {cores:{fundo,destaque,tinta}, fontes:{titulo,texto,destaque}}. cenas [{papel, tipo?, fala?, titulo, itens, valor, rotulo, antes, depois, texto, autor, botao}] (o diretor escolhe entre 38 primitivas, 15 transições, 8 câmeras e 8 tipografias, com assinatura própria; a palavra-chave vai entre *asteriscos*). '
156
156
  + 'elementos (termos reais do assunto). transicoes. camera. tipografia. semente. voz (texto ou {}; o whisper mede cada palavra). trilha (true ou o que buscar; false desliga). Sem cenas, o diretor monta. Prefira esta a escrever JSX. Sai em video-motion.mp4. previa:true = rascunho em segundos (meia resolução, sem mix final) para aprovar antes do final. gosto:false pula a tela de gosto do primeiro pedido; o gosto salvo da pessoa (~/.primocode/gosto-motion.json) já entra como peso no diretor — trate como direção, não molde.' },
157
157
  { tool: 'estudio_editar', escreve: true, faz: 'o editor por comando: muda um vídeo do estudio_motion (texto, cor, fonte, ritmo, cenas, trilha, volume, formato) e guarda versões',
@@ -294,6 +294,10 @@ const GRUPOS = [
294
294
  args: 'pedido: o que o usuário quer, com as palavras dele. pasta (opcional): onde construir — sem ela, uma pasta nova com o nome do pedido. rodadas (opcional, 1 a 4, padrão 2): quantas voltas de correção. '
295
295
  + 'USE quando pedirem um app, um site, uma página ou uma ferramenta INTEIRA, do zero. NÃO use para mexer no que já existe, para um arquivo só, nem para conserto: são seis a oito chamadas de agente, '
296
296
  + 'e gastá-las para trocar a cor de um botão come o dia do usuário. O revisor ABRE a página num navegador invisível e mede — página em branco e erro de JavaScript reprovam sozinhos, o que ele achar volta para o construtor.' },
297
+ // Só vai ao modelo quando o recorte por assunto tirou ferramentas (provedor com limite de TPM):
298
+ // lib/provider/local/recorte.js monta o schema real; aqui é o registro, e o tools.js atende.
299
+ { tool: 'mais_ferramentas', escreve: false, faz: 'liga um grupo de ferramentas que ficou de fora, na próxima volta',
300
+ args: 'grupo: o nome do grupo.' },
297
301
  { tool: 'finish', escreve: false, faz: 'encerra dizendo o que fez', noLoop: true,
298
302
  args: 'summary (opcional): uma frase do que foi feito.' },
299
303
  ],
package/lib/tool/tools.js CHANGED
@@ -1004,7 +1004,7 @@ async function despachar(name, args, ctx) {
1004
1004
  abrir: (arquivo) => { try { openInBrowser(arquivo); } catch {} },
1005
1005
  });
1006
1006
  case 'estudio_motion':
1007
- return estudio.ferramentaMotion(args);
1007
+ return estudio.ferramentaMotion(args, ctx);
1008
1008
  case 'estudio_editar':
1009
1009
  return estudio.ferramentaEditar(args);
1010
1010
  case 'estudio_narrar':
@@ -1083,6 +1083,10 @@ async function despachar(name, args, ctx) {
1083
1083
  return { ok: true, clicado: alvo.nome, tipo: alvo.tipo, x: alvo.x, y: alvo.y, duplo: !!args.double };
1084
1084
  }
1085
1085
 
1086
+ case 'mais_ferramentas':
1087
+ // O grupo é ligado pelo motor na próxima volta, lendo esta chamada na conversa (recorte.js).
1088
+ return { ok: true, grupo: String(args.grupo || ''), aviso: 'grupo ligado: as ferramentas dele estão disponíveis a partir da próxima chamada' };
1089
+
1086
1090
  default:
1087
1091
  /* O MODELO PRECISA CONSEGUIR SE CORRIGIR AQUI.
1088
1092
  "Tool não implementada: studio_criar" não diz o que existe,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "11.4.0",
3
+ "version": "11.5.0",
4
4
  "description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). 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": {
@@ -0,0 +1 @@
1
+ Inter 800 (ExtraBold), Google Fonts, licença SIL OFL 1.1. Embutida para a pele do Destrava funcionar sem rede (glifos.baixarFonte olha aqui primeiro).
Binary file
@@ -0,0 +1,6 @@
1
+ GSAP 3.15.0 (gsap.min.js, núcleo).
2
+ Origem: copiado de hyperframes@0.8.154 (dist/vendor/npm/gsap@3.15.0), o mesmo pacote que o PrimoCode já usa para renderizar.
3
+ Licença: GreenSock "Standard License" (https://gsap.com/standard-license). Desde a 3.13 o GSAP inteiro é gratuito, inclusive
4
+ uso comercial; a licença proíbe só montar um produto concorrente de ferramenta visual de animação. O cabeçalho original
5
+ com o aviso fica no topo do arquivo e não pode ser removido.
6
+ Por que está aqui: o HTML que a IA escreve para o Hyperframes não precisa de URL de CDN. O motor injeta/troca por este arquivo.