vibeeditor-render 0.4.0 → 0.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.
- package/core/ia.js +43 -5
- package/core/servidor.js +1 -0
- package/package.json +1 -1
package/core/ia.js
CHANGED
|
@@ -19,7 +19,11 @@ const path = require("node:path");
|
|
|
19
19
|
const crypto = require("node:crypto");
|
|
20
20
|
const {spawn, execFile} = require("node:child_process");
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// execuções ao mesmo tempo neste computador (cada uma é um `claude -p`); VIBE_IA_SIMULTANEAS muda (1 a 12)
|
|
23
|
+
const MAX_SIMULTANEAS = 6;
|
|
24
|
+
// sem notícia do site por este tempo (aba fechada ou recarregada no meio), a conversa é encerrada e libera a vaga.
|
|
25
|
+
// O site pergunta pelos eventos a cada ≤ 25 s e, enquanto roda uma ferramenta longa, avisa que está vivo (POST …/vivo).
|
|
26
|
+
const ABANDONO = 5 * 60000;
|
|
23
27
|
const ESPERA_EVENTOS = 25000;
|
|
24
28
|
const GUARDA_DEPOIS_DO_FIM = 10 * 60000; // o site pode reconectar e ler o fim depois
|
|
25
29
|
const PREFIXO_MCP = "mcp__vibe__";
|
|
@@ -129,11 +133,14 @@ function encerrar(p) {
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
/**
|
|
132
|
-
* opc: {dirDados, porta() (do servidor, para o MCP), env, log, spawn (injetável), acharClaude (injetável), versao
|
|
136
|
+
* opc: {dirDados, porta() (do servidor, para o MCP), env, log, spawn (injetável), acharClaude (injetável), versao,
|
|
137
|
+
* abandono (ms, injetável nos testes)}
|
|
133
138
|
*/
|
|
134
139
|
function criarIa(opc) {
|
|
135
140
|
const env = opc.env || process.env;
|
|
136
141
|
const log = opc.log || (() => {});
|
|
142
|
+
const maxSimultaneas = Math.min(12, Math.max(1, Math.floor(Number(env.VIBE_IA_SIMULTANEAS)) || MAX_SIMULTANEAS));
|
|
143
|
+
const abandono = opc.abandono || ABANDONO;
|
|
137
144
|
const rodar = opc.spawn || spawn;
|
|
138
145
|
const dir = path.join(opc.dirDados, "ia");
|
|
139
146
|
const trabalho = path.join(dir, "trabalho"); // cwd fixo: o --resume procura a sessão pela pasta
|
|
@@ -236,8 +243,9 @@ function criarIa(opc) {
|
|
|
236
243
|
|
|
237
244
|
async function criar(corpo) {
|
|
238
245
|
const d = validarConversa(corpo);
|
|
246
|
+
varrer();
|
|
239
247
|
const ativas = [...conversas.values()].filter((c) => !c.fim).length;
|
|
240
|
-
if (ativas >=
|
|
248
|
+
if (ativas >= maxSimultaneas) throw erro(429, `Já há ${ativas} execuções da IA neste computador (o limite é ${maxSimultaneas}). Espere uma terminar ou pare uma delas.`);
|
|
241
249
|
const b = bin();
|
|
242
250
|
if (!b) throw erro(409, "Não encontrei o Claude Code neste computador. Instale (code.claude.com) e faça o login com a sua conta.");
|
|
243
251
|
|
|
@@ -252,7 +260,7 @@ function criarIa(opc) {
|
|
|
252
260
|
fs.writeFileSync(arqMcp, JSON.stringify({mcpServers: {vibe: {type: "http", url: `http://127.0.0.1:${opc.porta()}/v1/ia/mcp/${id}`, headers: {Authorization: `Bearer ${token}`}}}}));
|
|
253
261
|
|
|
254
262
|
const c = {id, token, pasta, ferramentas: d.ferramentas, eventos: [], ouvintes: new Set(), pendentes: new Map(), web: new Map(),
|
|
255
|
-
sessao: d.sessao, ultimoTexto: "", resultado: null, fim: null, proc: null, saidaErro: ""};
|
|
263
|
+
sessao: d.sessao, ultimoTexto: "", resultado: null, fim: null, proc: null, saidaErro: "", vistoEm: Date.now()};
|
|
256
264
|
conversas.set(id, c);
|
|
257
265
|
|
|
258
266
|
const p = rodar(b, argumentos({...d, arqSistema, arqMcp}), {cwd: trabalho, env: ambiente(env), windowsHide: true, stdio: ["pipe", "pipe", "pipe"]});
|
|
@@ -291,6 +299,7 @@ function criarIa(opc) {
|
|
|
291
299
|
function eventos(id, desde, sinal) {
|
|
292
300
|
const c = conversas.get(id);
|
|
293
301
|
if (!c) return Promise.resolve(null);
|
|
302
|
+
c.vistoEm = Date.now();
|
|
294
303
|
const pegar = () => ({eventos: c.eventos.slice(desde), fim: !!c.fim});
|
|
295
304
|
if (c.eventos.length > desde || c.fim) return Promise.resolve(pegar());
|
|
296
305
|
return new Promise((ok) => {
|
|
@@ -312,6 +321,7 @@ function criarIa(opc) {
|
|
|
312
321
|
function resultado(id, corpo) {
|
|
313
322
|
const c = conversas.get(id);
|
|
314
323
|
if (!c) throw erro(404, "conversa não encontrada");
|
|
324
|
+
c.vistoEm = Date.now();
|
|
315
325
|
const p = c.pendentes.get(String(corpo && corpo.chamada));
|
|
316
326
|
if (!p) throw erro(404, "chamada não encontrada (já respondida ou a conversa terminou)");
|
|
317
327
|
c.pendentes.delete(String(corpo.chamada));
|
|
@@ -319,6 +329,34 @@ function criarIa(opc) {
|
|
|
319
329
|
return {ok: true};
|
|
320
330
|
}
|
|
321
331
|
|
|
332
|
+
/** o site ainda está com a conversa aberta (ele chama enquanto roda uma ferramenta demorada) */
|
|
333
|
+
function vivo(id) {
|
|
334
|
+
const c = conversas.get(id);
|
|
335
|
+
if (!c) throw erro(404, "conversa não encontrada");
|
|
336
|
+
c.vistoEm = Date.now();
|
|
337
|
+
return {ok: true, fim: !!c.fim};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Conversa sem notícia do site há mais de `abandono` (a aba fechou ou recarregou no meio): o claude estaria esperando
|
|
342
|
+
* para sempre a resposta de uma ferramenta que ninguém vai rodar. Encerra e libera a vaga.
|
|
343
|
+
*/
|
|
344
|
+
function varrer(agora = Date.now()) {
|
|
345
|
+
for (const c of conversas.values()) {
|
|
346
|
+
if (c.fim || agora - c.vistoEm <= abandono) continue;
|
|
347
|
+
log("ia", `conversa ${c.id.slice(0, 8)} sem o site há ${Math.round((agora - c.vistoEm) / 60000)} min: encerrada (aba fechada no meio?)`);
|
|
348
|
+
c.cancelado = true;
|
|
349
|
+
c.abandonada = true;
|
|
350
|
+
for (const [, p] of c.pendentes) p.ok({erro: true, conteudo: "o site fechou a conversa"});
|
|
351
|
+
c.pendentes.clear();
|
|
352
|
+
encerrar(c.proc);
|
|
353
|
+
// processo que não fecha (ou já tinha fechado sem avisar): a vaga não pode ficar presa
|
|
354
|
+
setTimeout(() => terminar(c, {motivo: "parado", sessao: c.sessao}), 15000).unref();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const relogio = setInterval(() => varrer(), 30000);
|
|
358
|
+
if (relogio.unref) relogio.unref();
|
|
359
|
+
|
|
322
360
|
function cancelar(id) {
|
|
323
361
|
const c = conversas.get(id);
|
|
324
362
|
if (!c) throw erro(404, "conversa não encontrada");
|
|
@@ -369,7 +407,7 @@ function criarIa(opc) {
|
|
|
369
407
|
}
|
|
370
408
|
}
|
|
371
409
|
|
|
372
|
-
return {status, criar, eventos, resultado, cancelar, mcp, cancelarTodas, _conversas: conversas};
|
|
410
|
+
return {status, criar, eventos, resultado, vivo, cancelar, mcp, cancelarTodas, _conversas: conversas, _varrer: varrer, maxSimultaneas};
|
|
373
411
|
}
|
|
374
412
|
|
|
375
413
|
module.exports = {criarIa, acharClaude, argumentos, paraMcp, validarConversa, erroDoClaude, ambiente};
|
package/core/servidor.js
CHANGED
|
@@ -192,6 +192,7 @@ function criarServidor(opc) {
|
|
|
192
192
|
}
|
|
193
193
|
if (partes[4] === "resultado" && req.method === "POST") return json(200, opc.ia.resultado(id, await lerJson(req, LIMITES.jsonBytes)), cors);
|
|
194
194
|
if (partes[4] === "cancelar" && req.method === "POST") return json(200, opc.ia.cancelar(id), cors);
|
|
195
|
+
if (partes[4] === "vivo" && req.method === "POST") return json(200, opc.ia.vivo(id), cors);
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
if (partes[0] === "v1" && partes[1] === "jobs" && partes[2]) {
|
package/package.json
CHANGED