@orkastery/cli 0.5.0 → 0.5.3
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/adapters/claude-code/README.md +11 -2
- package/adapters/claude-code/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/claude-code/commands/ork.md +9 -0
- package/adapters/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
- package/adapters/openclaw/README.md +35 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +47 -12
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +50 -12
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +59 -11
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +6 -2
- package/dist/board.js +16 -14
- package/dist/branch-de-estado.js +66 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/ci.js +8 -1
- package/dist/claim-lint.js +3 -1
- package/dist/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +110 -3
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +294 -37
- package/dist/entrega-pr.js +27 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +35 -9
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- package/dist/fix.js +3 -3
- package/dist/forja.js +31 -7
- package/dist/gates.js +5 -1
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +33 -1
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/horario.js +27 -0
- package/dist/hosts.js +59 -13
- package/dist/impedimento.js +205 -0
- package/dist/index.js +433 -88
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +28 -5
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +267 -30
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +680 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +12 -0
- package/dist/licoes.js +6 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +7 -1
- package/dist/manifest.js +44 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +36 -3
- package/dist/mcp-git.js +80 -15
- package/dist/mcp-grafo-worker.js +93 -0
- package/dist/mcp-grafo.js +279 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +23 -1
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +195 -20
- package/dist/ocupacao.js +17 -5
- package/dist/onboarding.js +9 -4
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +14 -11
- package/dist/parado-no-condutor.js +1089 -0
- package/dist/phase.js +68 -15
- package/dist/policies.js +79 -3
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +414 -0
- package/dist/pulse-delivery.js +60 -7
- package/dist/pulse.js +99 -3
- package/dist/ratelimit.js +20 -4
- package/dist/recall.js +3 -1
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +336 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +69 -2
- package/dist/roadmap-status.js +162 -7
- package/dist/runtime-context.js +5 -1
- package/dist/runtime-profiles.js +22 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-watcher-claude.js +113 -23
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -12
- package/dist/sessoes.js +87 -3
- package/dist/ship.js +110 -20
- package/dist/thread.js +212 -14
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +34 -8
- package/eval/casos/onboarding.json +1 -1
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/skills/README.md +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
- package/skills/phases/ship-release/SKILL.md +6 -3
|
@@ -38,6 +38,7 @@ exports.normalizarNativo = normalizarNativo;
|
|
|
38
38
|
exports.stopCorrelacionado = stopCorrelacionado;
|
|
39
39
|
exports.bloqueioPendente = bloqueioPendente;
|
|
40
40
|
exports.classificarSessaoClaude = classificarSessaoClaude;
|
|
41
|
+
exports.ultimoErroDeApi = ultimoErroDeApi;
|
|
41
42
|
exports.falhaDeContaDaTranscricao = falhaDeContaDaTranscricao;
|
|
42
43
|
exports.comFalhaDeConta = comFalhaDeConta;
|
|
43
44
|
exports.processoNativo = processoNativo;
|
|
@@ -268,22 +269,27 @@ function textoDoErroDeApi(e) {
|
|
|
268
269
|
const codigo = typeof e.error === 'string' ? e.error : '';
|
|
269
270
|
return (0, hitl_1.redigirSegredos)([codigo, textos.filter(Boolean).join(' ')].filter(Boolean).join(': ')).slice(0, 4096);
|
|
270
271
|
}
|
|
272
|
+
const UUID = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
273
|
+
/** A transcricao da sessao no diretorio do perfil (ou no implicito, sem perfil); `null` com sessao que nao e uuid. */
|
|
274
|
+
function arquivoDaTranscricao(perfil, cwd, sessionId) {
|
|
275
|
+
if (!UUID.test(sessionId))
|
|
276
|
+
return null;
|
|
277
|
+
return path.join((0, runtime_profiles_1.diretorioEfetivo)('claude-bg', perfil), 'projects', path.resolve(cwd).replace(/[^a-zA-Z0-9-]/g, '-'), `${sessionId}.jsonl`);
|
|
278
|
+
}
|
|
271
279
|
/**
|
|
272
|
-
* I-33 (D11b):
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
* (N3), o que encerrou a sessao. O codigo `rate_limit`
|
|
277
|
-
* do Claude Code cobre tanto o limite do plano quanto o 429 transitorio ("Request rejected (429)",
|
|
278
|
-
* "not your usage limit"): quem separa e o criterio unico da D16 (`naturezaDoLimite`), pela frase.
|
|
280
|
+
* I-33 (D11b): o erro de API que o proprio Claude Code gravou na transcricao da sessao. So entram linhas marcadas
|
|
281
|
+
* como erro de API (`isApiErrorMessage` ou campo `error`): texto do agente nunca vira motivo. Leitura limitada da
|
|
282
|
+
* cauda, sem seguir link; ausencia de transcricao e ausencia de evidencia. Vale o ULTIMO erro de API (N3), o que
|
|
283
|
+
* encerrou a sessao. RM-037 (fatia 5): com ele vem o instante da linha, que e a ancora da hora dita.
|
|
279
284
|
*/
|
|
280
|
-
function
|
|
281
|
-
|
|
285
|
+
function ultimoErroDeApi(perfil, cwd, sessionId) {
|
|
286
|
+
const arquivo = arquivoDaTranscricao(perfil, cwd, sessionId);
|
|
287
|
+
if (!arquivo)
|
|
282
288
|
return null;
|
|
283
|
-
const arquivo = path.join((0, runtime_profiles_1.diretorioEfetivo)('claude-bg', perfil), 'projects', path.resolve(cwd).replace(/[^a-zA-Z0-9-]/g, '-'), `${sessionId}.jsonl`);
|
|
284
289
|
let fd;
|
|
290
|
+
// O_NONBLOCK: um FIFO no lugar da transcricao nao trava o observador no open (sugestao da rodada 1 do CHECK).
|
|
285
291
|
try {
|
|
286
|
-
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
292
|
+
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
287
293
|
}
|
|
288
294
|
catch {
|
|
289
295
|
return null;
|
|
@@ -300,7 +306,7 @@ function falhaDeContaDaTranscricao(perfil, cwd, sessionId, agoraMs = Date.now())
|
|
|
300
306
|
finally {
|
|
301
307
|
fs.closeSync(fd);
|
|
302
308
|
}
|
|
303
|
-
|
|
309
|
+
let ultimo = null;
|
|
304
310
|
for (const linha of cauda.split('\n')) {
|
|
305
311
|
let e;
|
|
306
312
|
try {
|
|
@@ -311,14 +317,82 @@ function falhaDeContaDaTranscricao(perfil, cwd, sessionId, agoraMs = Date.now())
|
|
|
311
317
|
}
|
|
312
318
|
if (!e || typeof e !== 'object' || (e.isApiErrorMessage !== true && e.error === undefined))
|
|
313
319
|
continue;
|
|
314
|
-
|
|
320
|
+
const ms = typeof e.timestamp === 'string' ? Date.parse(e.timestamp) : NaN;
|
|
321
|
+
ultimo = { texto: textoDoErroDeApi(e), em: Number.isFinite(ms) ? new Date(ms).toISOString() : null,
|
|
322
|
+
uuid: typeof e.uuid === 'string' && UUID.test(e.uuid) ? e.uuid.toLowerCase() : null };
|
|
315
323
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
return ultimo;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* I-33 (D11b): falha de conta registrada pelo Claude Code na transcricao da sessao. O codigo `rate_limit` do
|
|
328
|
+
* Claude Code cobre tanto o limite do plano quanto o 429 transitorio ("Request rejected (429)", "not your usage
|
|
329
|
+
* limit"): quem separa e o criterio unico da D16 (`naturezaDoLimite`), pela frase. I-33 (N3): o criterio roda
|
|
330
|
+
* sobre o erro que encerrou a sessao, nunca sobre os anteriores juntos. RM-037 (fatia 5): a hora dita e ancorada
|
|
331
|
+
* no instante da mensagem; sem instante na linha, no relogio de quem le, como antes.
|
|
332
|
+
*/
|
|
333
|
+
function falhaDeContaDaTranscricao(perfil, cwd, sessionId, agoraMs = Date.now()) {
|
|
334
|
+
return falhaDoErro(ultimoErroDeApi(perfil, cwd, sessionId), agoraMs);
|
|
335
|
+
}
|
|
336
|
+
function falhaDoErro(erro, agoraMs) {
|
|
337
|
+
return erro ? (0, claude_bg_1.parseFalhaDeConta)(erro.texto, erro.em ? Date.parse(erro.em) : agoraMs) : null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* RM-037 (fatia 5): a cota vista com a sessao viva. Em 02/10/2026 as sessoes de uma conta pararam as 06:00Z no
|
|
341
|
+
* limite de gasto ("your session limit resets 4:40am (<fuso>)"), sem Stop e com o processo vivo por mais uma
|
|
342
|
+
* hora; o perfil so saiu do rodizio as 07:15Z, quando o processo morreu, e o redespacho das 06:14Z caiu na mesma
|
|
343
|
+
* conta. Aqui, a cada volta com a transcricao maior que na anterior, o ultimo erro de API depois do despacho passa
|
|
344
|
+
* pelo criterio unico da D16; esgotamento com prazo futuro marca o perfil ate a hora dita (ou a janela padrao
|
|
345
|
+
* contada da mensagem) e grava `runtime_quota_detected` uma vez por erro. A fase nao fecha e a conducao nao muda:
|
|
346
|
+
* a sessao continua viva, e o fecho segue o caminho de sempre, sem marcar de novo o mesmo erro.
|
|
347
|
+
*/
|
|
348
|
+
function cotaAoVivo(ctx, perfil, cwd, cursor, erroDaTranscricao, gravar) {
|
|
349
|
+
const { sessao, dir } = ctx;
|
|
350
|
+
const arquivo = arquivoDaTranscricao(perfil, cwd, sessao.sessionId);
|
|
351
|
+
let tamanho;
|
|
352
|
+
try {
|
|
353
|
+
const st = arquivo ? fs.lstatSync(arquivo) : null;
|
|
354
|
+
// Link, FIFO ou diretorio no lugar da transcricao nao e transcricao: nada a ler.
|
|
355
|
+
tamanho = st?.isFile() ? st.size : NaN;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!Number.isFinite(tamanho) || cursor.transcricaoBytes === tamanho)
|
|
361
|
+
return;
|
|
362
|
+
cursor.transcricaoBytes = tamanho;
|
|
363
|
+
const erro = erroDaTranscricao();
|
|
364
|
+
const erroMs = erro?.em ? Date.parse(erro.em) : NaN;
|
|
365
|
+
// Linha sem instante, ou de antes deste despacho (sessao retomada), nao prova que a cota e deste turno.
|
|
366
|
+
if (!erro || !Number.isFinite(erroMs) || erroMs < Date.parse(sessao.despachadaEm))
|
|
367
|
+
return;
|
|
368
|
+
const falha = falhaDoErro(erro, ctx.agoraMs);
|
|
369
|
+
if (falha?.motivo !== 'runtime.quota-exhausted' || !ctx.carregado)
|
|
370
|
+
return;
|
|
371
|
+
const esgotadoAte = (0, ratelimit_1.prazoDaCotaVista)(ctx.carregado.manifesto, falha, erroMs, ctx.agoraMs);
|
|
372
|
+
if (!esgotadoAte)
|
|
373
|
+
return;
|
|
374
|
+
// A chave do erro: o uuid da linha ou, sem ele, o instante e o trecho (dado de maquina, nunca texto).
|
|
375
|
+
const sensorEventId = (0, node_crypto_1.createHash)('sha256')
|
|
376
|
+
.update(JSON.stringify([sessao.sessionId, sessao.despachadaEm, erro.uuid ?? [erro.em, falha.trecho]])).digest('hex');
|
|
377
|
+
(0, session_events_1.comLockDeSessao)(dir, () => {
|
|
378
|
+
if ((0, ledger_1.lerLedger)(dir).some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.cotaVistaNaTranscricao && e.sensorEventId === sensorEventId))
|
|
379
|
+
return;
|
|
380
|
+
if (perfil) {
|
|
381
|
+
try {
|
|
382
|
+
(0, ratelimit_1.marcarContaDaFalha)(ctx.carregado, perfil, falha, ctx.agoraMs, esgotadoAte);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// Store ocupado ou invalido: a proxima volta le a transcricao de novo e tenta outra vez.
|
|
386
|
+
delete cursor.transcricaoBytes;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
gravar(ledger_1.TIPOS_DE_EVENTO.cotaVistaNaTranscricao, { fase: sessao.fase, sessionId: sessao.sessionId, despachoEm: sessao.despachadaEm,
|
|
391
|
+
runtime: 'claude-bg', sensorEventId, motivo: falha.motivo, erroEm: erro.em, resetEm: falha.resetEm, fonteDoPrazo: falha.fonte,
|
|
392
|
+
// O trecho ja vem redigido e numa linha; sem controle, como no store de perfis.
|
|
393
|
+
esgotadoAte, trecho: falha.trecho.replace(/\p{Cc}/gu, ' '), perfil: perfil ? { id: perfil.id, runtime: perfil.runtime } : null,
|
|
394
|
+
perfilMarcado: !!perfil, origem: 'sessions.watch' });
|
|
395
|
+
});
|
|
322
396
|
}
|
|
323
397
|
/**
|
|
324
398
|
* I-33 (D11): precedencia entre a prova da I-34 e a falha de conta. Com Stop correlacionado vale
|
|
@@ -360,8 +434,10 @@ function estadoDoArtefato(raiz, threadId, arquivo) {
|
|
|
360
434
|
const file = path.join((0, thread_1.dirThread)(raiz, threadId), arquivo);
|
|
361
435
|
const vazio = { sha256: null, mtimeMs: null, texto: null };
|
|
362
436
|
let fd;
|
|
437
|
+
// RM-037 (fatia 5, sugestao da rodada 2 do CHECK): O_NONBLOCK, como na transcricao, para um FIFO no lugar do
|
|
438
|
+
// artefato nao travar o observador no open; o `isFile` abaixo o recusa.
|
|
363
439
|
try {
|
|
364
|
-
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
440
|
+
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
365
441
|
}
|
|
366
442
|
catch (erro) {
|
|
367
443
|
return erro.code === 'ENOENT' ? vazio : { ...vazio, invalido: true };
|
|
@@ -600,9 +676,13 @@ function observarClaudeSobLock(ctx) {
|
|
|
600
676
|
const registro = consulta.ok ? consulta.registros.find(s => s.sessionId === sessao.sessionId && s.cwd === fonte.cwd) ?? null : null;
|
|
601
677
|
const artefato = saidaDoArtefato(raiz, thread.id, sessao, fonte, eventos);
|
|
602
678
|
let prova;
|
|
679
|
+
// RM-037 (fatia 5): o ultimo erro de API da transcricao, lido no maximo uma vez por volta, serve a cota ao vivo
|
|
680
|
+
// e ao fecho.
|
|
681
|
+
let erroLido;
|
|
682
|
+
const erroDaTranscricao = () => erroLido !== undefined ? erroLido : (erroLido = ultimoErroDeApi(perfil, fonte.cwd, sessao.sessionId));
|
|
603
683
|
const { c, falha } = comFalhaDeConta(classificarSessaoClaude({ sessao, eventos, consulta, registro, cursor, agoraMs: ctx.agoraMs,
|
|
604
684
|
limiteMs: ctx.limiteMs, pausaAoFim: (0, thread_1.pausaNaThread)(thread, sessao.fase),
|
|
605
|
-
prova: () => prova ??= provaDoOrk(raiz, thread.id, sessao, eventos, fonte), processo: processoNativo }), () =>
|
|
685
|
+
prova: () => prova ??= provaDoOrk(raiz, thread.id, sessao, eventos, fonte), processo: processoNativo }), () => falhaDoErro(erroDaTranscricao(), ctx.agoraMs));
|
|
606
686
|
(0, codex_runner_1.gravarAtomico)(arquivoCursor, cursor);
|
|
607
687
|
// GO-FIX 3 (achado 1): cursor gravado numa thread que sumiu durante a consulta é desfeito aqui;
|
|
608
688
|
// as pastas criadas pela observação, pelo `session-watcher`.
|
|
@@ -610,6 +690,12 @@ function observarClaudeSobLock(ctx) {
|
|
|
610
690
|
fs.rmSync(arquivoCursor, { force: true });
|
|
611
691
|
throw new Error(exports.THREAD_REMOVIDA);
|
|
612
692
|
}
|
|
693
|
+
if (!c.terminal) {
|
|
694
|
+
const lidos = cursor.transcricaoBytes;
|
|
695
|
+
cotaAoVivo(ctx, perfil, fonte.cwd, cursor, erroDaTranscricao, gravar);
|
|
696
|
+
if (cursor.transcricaoBytes !== lidos)
|
|
697
|
+
(0, codex_runner_1.gravarAtomico)(arquivoCursor, cursor);
|
|
698
|
+
}
|
|
613
699
|
if (!c.terminal && c.inconclusivo)
|
|
614
700
|
return (0, session_events_1.comLockDeSessao)(dir, () => {
|
|
615
701
|
const diagnosticoId = `${resultadoId}:inconclusivo`;
|
|
@@ -649,8 +735,12 @@ function observarClaudeSobLock(ctx) {
|
|
|
649
735
|
observadoEm, gate: 'phase.dispatch', origem: 'sessions.watch' };
|
|
650
736
|
if (!atuais.some(e => e.tipo === c.classificacao && e.sensorResultId === resultadoId))
|
|
651
737
|
gravar(c.classificacao, dados);
|
|
652
|
-
// I-33 (D12): o perfil sai do rodizio na primeira gravacao do resultado, onde a evidencia esta.
|
|
653
|
-
|
|
738
|
+
// I-33 (D12): o perfil sai do rodizio na primeira gravacao do resultado, onde a evidencia esta. RM-037 (fatia 5):
|
|
739
|
+
// o erro que a cota ao vivo ja marcou nao marca de novo; depois do prazo dito, marcar de novo tiraria do
|
|
740
|
+
// rodizio uma conta que ja voltou.
|
|
741
|
+
const jaVista = !!erroLido?.em && atuais.some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.cotaVistaNaTranscricao &&
|
|
742
|
+
e.sessionId === sessao.sessionId && e.despachoEm === sessao.despachadaEm && e.erroEm === erroLido.em);
|
|
743
|
+
if (falha && perfil && ctx.carregado && !jaVista && !atuais.some(e => e.tipo === 'phase_result' && e.sensorResultId === resultadoId)) {
|
|
654
744
|
try {
|
|
655
745
|
(0, ratelimit_1.marcarContaDaFalha)(ctx.carregado, perfil, falha, ctx.agoraMs);
|
|
656
746
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.pidVivo = pidVivo;
|
|
37
|
+
exports.ehFantasma = ehFantasma;
|
|
38
|
+
exports.contasDeSessoes = contasDeSessoes;
|
|
39
|
+
exports.consultarContas = consultarContas;
|
|
40
|
+
exports.estadosNasContas = estadosNasContas;
|
|
41
|
+
exports.sessoesVivasPorPerfil = sessoesVivasPorPerfil;
|
|
42
|
+
/**
|
|
43
|
+
* RM-056 (D4, D5): as sessoes desta maquina em TODAS as contas que o `ork` conhece.
|
|
44
|
+
*
|
|
45
|
+
* O `claude agents` e o `~/.codex/sessions` so respondem pela conta do ambiente de quem consulta:
|
|
46
|
+
* com perfis (`ork accounts`), uma sessao despachada pelo perfil X so aparece consultando com o
|
|
47
|
+
* `CLAUDE_CONFIG_DIR`/`CODEX_HOME` de X. Aqui a consulta passa pela conta do processo e por cada
|
|
48
|
+
* perfil do store, sem repetir diretorio, e cada sessao volta com o id do perfil dela (nunca o
|
|
49
|
+
* diretorio nem nada de dentro dele).
|
|
50
|
+
*
|
|
51
|
+
* Fantasma (D5): sessao claude-bg com estado nao terminal sem pid, ou com pid sem processo. O
|
|
52
|
+
* `claude agents` guarda o registro, mas nao ha quem trabalhe: ela nao ocupa vaga, e
|
|
53
|
+
* `ork sessions limpar-fantasmas` solta o vinculo do `ork` sem tocar no runtime.
|
|
54
|
+
*/
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const path = __importStar(require("node:path"));
|
|
57
|
+
const claude_bg_1 = require("./adapters/claude-bg");
|
|
58
|
+
const codex_1 = require("./adapters/codex");
|
|
59
|
+
const runtime_profiles_1 = require("./runtime-profiles");
|
|
60
|
+
const TERMINAIS = ['done', 'completed', 'exited', 'stopped', 'failed'];
|
|
61
|
+
const estadoDe = (s) => String(s.state ?? s.status ?? '').trim().toLowerCase();
|
|
62
|
+
/** O processo existe? `EPERM` e processo de outro usuario: existe. */
|
|
63
|
+
function pidVivo(pid) {
|
|
64
|
+
if (typeof pid !== 'number' || !Number.isSafeInteger(pid) || pid <= 0)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
process.kill(pid, 0);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return e.code === 'EPERM';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** D5: fantasma e so claude-bg (o codex nao expoe pid na fonte), em estado nao terminal. */
|
|
75
|
+
function ehFantasma(runtime, s, vivo = pidVivo) {
|
|
76
|
+
return runtime === 'claude-bg' && !TERMINAIS.includes(estadoDe(s)) && !vivo(s.pid);
|
|
77
|
+
}
|
|
78
|
+
const real = (dir) => { try {
|
|
79
|
+
return fs.realpathSync(dir);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return path.resolve(dir);
|
|
83
|
+
} };
|
|
84
|
+
/**
|
|
85
|
+
* D4: as contas a consultar. Os perfis do store vem primeiro (inclusive desativado, que ainda pode
|
|
86
|
+
* ter sessao viva), para a sessao levar o id do perfil quando a conta do processo e a mesma pasta.
|
|
87
|
+
*/
|
|
88
|
+
function contasDeSessoes(raiz, env = process.env) {
|
|
89
|
+
const contas = [], fontes = [];
|
|
90
|
+
const vistos = new Set();
|
|
91
|
+
const incluir = (runtime, perfil, dir) => {
|
|
92
|
+
const chave = `${runtime}:${real(dir)}`;
|
|
93
|
+
if (vistos.has(chave))
|
|
94
|
+
return;
|
|
95
|
+
vistos.add(chave);
|
|
96
|
+
contas.push({ runtime, perfil, dir });
|
|
97
|
+
};
|
|
98
|
+
if (raiz) {
|
|
99
|
+
try {
|
|
100
|
+
for (const p of (0, runtime_profiles_1.lerPerfis)(raiz).perfis) {
|
|
101
|
+
const d = (0, runtime_profiles_1.perfilDeDespacho)(p);
|
|
102
|
+
incluir(d.runtime, d, (d.configDir ?? d.codexHome));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
fontes.push({ origem: 'perfis de conta (ork accounts)', ok: false, detalhe: e.message });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const runtime of runtime_profiles_1.RUNTIMES_COM_PERFIL)
|
|
110
|
+
incluir(runtime, null, (0, runtime_profiles_1.diretorioImplicito)(runtime, env));
|
|
111
|
+
return { contas, fontes };
|
|
112
|
+
}
|
|
113
|
+
const rotulo = (c) => c.perfil ? `perfil ${c.perfil.id}` : 'conta do processo';
|
|
114
|
+
/**
|
|
115
|
+
* D4: as sessoes de todas as contas. Consulta que falha vira fonte `ok: false`, nunca "zero
|
|
116
|
+
* sessoes". `staleMin` e a janela de atividade que torna viva uma sessao codex (D3).
|
|
117
|
+
*/
|
|
118
|
+
function consultarContas(raiz, opcoes = {}) {
|
|
119
|
+
const base = opcoes.contas ? { contas: opcoes.contas, fontes: [] } : contasDeSessoes(raiz);
|
|
120
|
+
const fontes = [...base.fontes];
|
|
121
|
+
const sessoes = [];
|
|
122
|
+
const agoraMs = opcoes.agoraMs ?? Date.now();
|
|
123
|
+
const janelaMs = (opcoes.staleMin ?? 30) * 60_000;
|
|
124
|
+
for (const conta of base.contas) {
|
|
125
|
+
const perfil = conta.perfil?.id ?? null;
|
|
126
|
+
if (conta.runtime === 'claude-bg') {
|
|
127
|
+
let ambiente;
|
|
128
|
+
try {
|
|
129
|
+
ambiente = (0, claude_bg_1.ambienteDoPerfil)(conta.perfil);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
fontes.push({ origem: `claude agents (${rotulo(conta)})`, ok: false, detalhe: e.message });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const r = (0, claude_bg_1.consultarSessoes)(undefined, opcoes.todas, ambiente);
|
|
136
|
+
const origem = `claude agents --json${opcoes.todas ? ' --all' : ''} (${rotulo(conta)})`;
|
|
137
|
+
// Fatia 2 do ensaio da 0.5.0 (P1): só o ENOENT antes da consulta prova a fonte ausente; qualquer
|
|
138
|
+
// outra falha do `claude` continua deixando o inventário incompleto.
|
|
139
|
+
fontes.push(r.ausente
|
|
140
|
+
? { origem, ok: true, ausente: true, detalhe: `${r.detalhe}: nenhuma sessão claude-bg a listar`,
|
|
141
|
+
correcao: 'para despachar e listar pelo claude-bg, instale o Claude Code e garanta `claude` no PATH' }
|
|
142
|
+
: { origem, ok: r.ok, detalhe: r.detalhe });
|
|
143
|
+
for (const s of r.sessoes) {
|
|
144
|
+
const fantasma = ehFantasma('claude-bg', s);
|
|
145
|
+
sessoes.push({ ...s, runtime: 'claude-bg', perfil, fantasma, viva: !fantasma && !TERMINAIS.includes(estadoDe(s)) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const r = (0, codex_1.consultarRollouts)(opcoes.todas, conta.dir);
|
|
150
|
+
// A conta do processo segue com o caminho de antes; a do perfil vai pelo id, nunca pela pasta.
|
|
151
|
+
fontes.push(...(conta.perfil ? r.resultadosFontes.map(f => ({ ...f,
|
|
152
|
+
origem: `codex (${rotulo(conta)}): ${path.relative(conta.dir, f.origem)}` })) : r.resultadosFontes));
|
|
153
|
+
for (const s of r.sessoes) {
|
|
154
|
+
const recente = typeof s.atividadeEm === 'number' && agoraMs - s.atividadeEm <= janelaMs;
|
|
155
|
+
sessoes.push({ ...s, runtime: 'codex', perfil, fantasma: false, viva: estadoDe(s) !== 'completed' && recente });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { sessoes, fontes, contas: base.contas };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* D7: o mapa sessionId -> estado que o escalonador e o monitor usam, agora de todas as contas.
|
|
163
|
+
* Fantasma vira `fantasma` (nem `working` nem `blocked`: nao ocupa vaga nem pede o dono); codex
|
|
164
|
+
* vivo vira `working`. `null` ("runtime nao consultado") quando nenhuma conta claude-bg respondeu e
|
|
165
|
+
* nenhuma sessao codex apareceu: um mapa vazio ali diria "nada rodando" sem ter olhado.
|
|
166
|
+
*/
|
|
167
|
+
function estadosNasContas(raiz, opcoes = {}) {
|
|
168
|
+
const r = consultarContas(raiz, opcoes);
|
|
169
|
+
const claude = r.fontes.filter(f => f.origem.startsWith('claude agents'));
|
|
170
|
+
if (claude.length > 0 && !claude.some(f => f.ok) && !r.sessoes.some(s => s.runtime === 'codex'))
|
|
171
|
+
return null;
|
|
172
|
+
return new Map(r.sessoes.map(s => [s.sessionId,
|
|
173
|
+
s.fantasma ? 'fantasma' : s.runtime === 'codex' ? (s.viva ? 'working' : estadoDe(s)) : estadoDe(s)]));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* RM-056 (D3): sessoes vivas de cada perfil do runtime nesta maquina, a carga do rodizio por
|
|
177
|
+
* carga. So as contas dos perfis sao consultadas (a do processo nao recebe despacho com perfis).
|
|
178
|
+
*/
|
|
179
|
+
function sessoesVivasPorPerfil(raiz, runtime, staleMin, agoraMs) {
|
|
180
|
+
const contas = contasDeSessoes(raiz).contas.filter(c => c.runtime === runtime && c.perfil);
|
|
181
|
+
const carga = new Map(contas.map(c => [c.perfil.id, 0]));
|
|
182
|
+
for (const s of consultarContas(raiz, { contas, staleMin, agoraMs }).sessoes) {
|
|
183
|
+
if (s.viva && s.perfil)
|
|
184
|
+
carga.set(s.perfil, (carga.get(s.perfil) ?? 0) + 1);
|
|
185
|
+
}
|
|
186
|
+
return carga;
|
|
187
|
+
}
|
|
@@ -38,9 +38,8 @@ exports.inventariarSessoes = inventariarSessoes;
|
|
|
38
38
|
const fs = __importStar(require("node:fs"));
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
40
40
|
const os = __importStar(require("node:os"));
|
|
41
|
-
const claude_bg_1 = require("./adapters/claude-bg");
|
|
42
|
-
const codex_1 = require("./adapters/codex");
|
|
43
41
|
const estado_thread_1 = require("./estado-thread");
|
|
42
|
+
const sessoes_contas_1 = require("./sessoes-contas");
|
|
44
43
|
const thread_1 = require("./thread");
|
|
45
44
|
/** Projeto canônico mais próximo de cada cwd declarado, sem varrer o HOME inteiro. */
|
|
46
45
|
function raizesDeRegistros(cwds) {
|
|
@@ -71,16 +70,10 @@ function raizesDeRegistros(cwds) {
|
|
|
71
70
|
}
|
|
72
71
|
function inventariarSessoes(raiz, opcoes = {}) {
|
|
73
72
|
const canonica = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const fontes =
|
|
77
|
-
|
|
78
|
-
...codex.resultadosFontes,
|
|
79
|
-
];
|
|
80
|
-
const todas = [
|
|
81
|
-
...claude.sessoes.map(s => ({ ...s, runtime: 'claude-bg', vinculos: [] })),
|
|
82
|
-
...codex.sessoes.map(s => ({ ...s, runtime: 'codex', vinculos: [] })),
|
|
83
|
-
];
|
|
73
|
+
// RM-056 (D4): a conta do processo e cada perfil do store, sem repetir diretorio.
|
|
74
|
+
const contas = (0, sessoes_contas_1.consultarContas)(canonica, { todas: opcoes.todas });
|
|
75
|
+
const fontes = contas.fontes;
|
|
76
|
+
const todas = contas.sessoes.map(s => ({ ...s, vinculos: [] }));
|
|
84
77
|
// O cruzamento usa o universo global mesmo quando a apresentação é local.
|
|
85
78
|
try {
|
|
86
79
|
const projetos = raizesDeRegistros([canonica, ...todas.map(s => s.cwd)]);
|
|
@@ -155,5 +148,6 @@ function inventariarSessoes(raiz, opcoes = {}) {
|
|
|
155
148
|
escopo: { usuario: os.userInfo().username, global: !!opcoes.global, historico: !!opcoes.todas, raiz: canonica },
|
|
156
149
|
fontes, sessoes, total: sessoes.length,
|
|
157
150
|
semThread: sessoes.filter(s => s.vinculos.length === 0).length, ambiguas,
|
|
151
|
+
fantasmas: sessoes.filter(s => s.fantasma).length,
|
|
158
152
|
};
|
|
159
153
|
}
|
package/dist/sessoes.js
CHANGED
|
@@ -44,21 +44,34 @@ exports.tabelaDeSessoes = tabelaDeSessoes;
|
|
|
44
44
|
exports.logsDaSessao = logsDaSessao;
|
|
45
45
|
exports.pararSessao = pararSessao;
|
|
46
46
|
exports.comandoDeAttach = comandoDeAttach;
|
|
47
|
+
exports.limparFantasmas = limparFantasmas;
|
|
48
|
+
exports.sessoesSemFim = sessoesSemFim;
|
|
49
|
+
exports.textoDaLimpeza = textoDaLimpeza;
|
|
47
50
|
const adapter = __importStar(require("./adapters/claude-bg"));
|
|
48
51
|
const sessoes_inventario_1 = require("./sessoes-inventario");
|
|
49
52
|
const runtime_profiles_1 = require("./runtime-profiles");
|
|
50
53
|
const util_1 = require("./util");
|
|
54
|
+
const estado_thread_1 = require("./estado-thread");
|
|
55
|
+
const ledger_1 = require("./ledger");
|
|
56
|
+
const thread_1 = require("./thread");
|
|
51
57
|
function textoDoInventario(r) {
|
|
52
58
|
const linhas = [
|
|
53
59
|
`Inventário ${r.escopo.global ? 'global' : 'do projeto'} da conta ${r.escopo.usuario}; histórico: ${r.escopo.historico ? 'incluído' : 'omitido'}.`,
|
|
54
|
-
`Fontes: ${r.fontes.map(f => `${f.origem} (${f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
|
|
55
|
-
`Total: ${r.total}; sem thread: ${r.semThread}; ambíguas: ${r.ambiguas}; consulta: ${r.ok ? 'válida' : 'INCOMPLETA'}.`,
|
|
60
|
+
`Fontes: ${r.fontes.map(f => `${f.origem} (${f.ausente ? 'ausente' : f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
|
|
61
|
+
`Total: ${r.total}; sem thread: ${r.semThread}; ambíguas: ${r.ambiguas}; fantasmas: ${r.fantasmas}; consulta: ${r.ok ? 'válida' : 'INCOMPLETA'}.`,
|
|
56
62
|
];
|
|
63
|
+
// RM-056 (D4): a coluna PERFIL e o id do perfil (`processo` sem perfil), nunca o diretorio da conta.
|
|
57
64
|
if (r.sessoes.length)
|
|
58
|
-
linhas.push((0, util_1.tabela)(['ID', 'RUNTIME', 'ESTADO', 'THREAD/FASE'], r.sessoes.map(s => [s.sessionId, s.runtime, s.
|
|
65
|
+
linhas.push((0, util_1.tabela)(['ID', 'RUNTIME', 'PERFIL', 'ESTADO', 'THREAD/FASE'], r.sessoes.map(s => [s.sessionId, s.runtime, s.perfil ?? 'processo',
|
|
66
|
+
`${s.state ?? s.status ?? 'unknown'}${s.fantasma ? ' (fantasma)' : ''}`,
|
|
59
67
|
s.vinculos.map(v => `${v.thread}/${v.fase}`).join(', ') || '(fora do ork)'])));
|
|
68
|
+
if (r.fantasmas > 0)
|
|
69
|
+
linhas.push('Fantasma: registro sem processo vivo, não ocupa vaga; `ork sessions limpar-fantasmas` solta o vínculo do ork sem tocar no runtime.');
|
|
60
70
|
for (const f of r.fontes.filter(f => !f.ok))
|
|
61
71
|
linhas.push(`Falha: ${f.detalhe}`);
|
|
72
|
+
// Fatia 2 do ensaio da 0.5.0 (P1): a fonte ausente nao e falha, mas diz o que falta e como ter.
|
|
73
|
+
for (const f of r.fontes.filter(f => f.ausente))
|
|
74
|
+
linhas.push(`Ausente: ${f.detalhe}${f.correcao ? `; correção: ${f.correcao}` : ''}`);
|
|
62
75
|
return linhas.join('\n');
|
|
63
76
|
}
|
|
64
77
|
function tabelaDeSessoes(raiz, todas = false) {
|
|
@@ -117,3 +130,74 @@ function comandoDeAttach(chave, raiz) {
|
|
|
117
130
|
const { sessao, perfil, configDir } = l.achada;
|
|
118
131
|
return { texto: adapter.comandoAttach(sessao.sessionId, perfil ? configDir : null), codigo: 0 };
|
|
119
132
|
}
|
|
133
|
+
const ENCERRAM_A_SESSAO = ['phase_result', 'sessao_morta', 'session_superseded'];
|
|
134
|
+
/**
|
|
135
|
+
* RM-056 (D6): solta o vinculo do `ork` com cada sessao fantasma (sem pid e sem processo) deste
|
|
136
|
+
* projeto, gravando `sessao_morta` no ledger da thread: e o evento que libera a conducao
|
|
137
|
+
* (`fimDaSessao`) e a vaga. Nunca chama o runtime: o registro no `claude agents` fica onde esta.
|
|
138
|
+
*/
|
|
139
|
+
function limparFantasmas(raiz, opcoes = {}) {
|
|
140
|
+
const canonica = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
141
|
+
const inv = (0, sessoes_inventario_1.inventariarSessoes)(canonica, { global: true });
|
|
142
|
+
const itens = [];
|
|
143
|
+
for (const s of inv.sessoes.filter(x => x.fantasma)) {
|
|
144
|
+
// Fechamento (RM-056, ao fechar): so os vinculos da thread que fecha; o resto fica como estava.
|
|
145
|
+
const vinculos = opcoes.thread ? s.vinculos.filter(v => v.thread === opcoes.thread && v.raiz === canonica) : s.vinculos;
|
|
146
|
+
if (opcoes.thread && vinculos.length === 0)
|
|
147
|
+
continue;
|
|
148
|
+
if (vinculos.length === 0) {
|
|
149
|
+
itens.push({ sessionId: s.sessionId, perfil: s.perfil, thread: null, fase: null, acao: 'sem-thread' });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const v of vinculos) {
|
|
153
|
+
const base = { sessionId: s.sessionId, perfil: s.perfil, thread: v.thread, fase: v.fase };
|
|
154
|
+
if (v.raiz !== canonica) {
|
|
155
|
+
itens.push({ ...base, acao: 'outro-projeto' });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const dir = (0, thread_1.dirThread)(canonica, v.thread);
|
|
159
|
+
if ((0, ledger_1.lerLedger)(dir).some(e => ENCERRAM_A_SESSAO.includes(e.tipo) && e.sessionId === s.sessionId)) {
|
|
160
|
+
itens.push({ ...base, acao: 'ja-solto' });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!opcoes.dryRun)
|
|
164
|
+
(0, ledger_1.registrar)(dir, v.thread, 'sessao_morta', {
|
|
165
|
+
fase: v.fase, sessionId: s.sessionId, runtime: s.runtime, ...(s.perfil ? { perfilId: s.perfil } : {}),
|
|
166
|
+
origem: opcoes.origem ?? 'sessions.limpar-fantasmas', estadoNoRuntime: s.state ?? s.status ?? null,
|
|
167
|
+
evidencia: 'claude agents lista a sessao sem pid ou com pid sem processo (fantasma, RM-056 D5)',
|
|
168
|
+
razao: 'solta o vinculo do ork (conducao e vaga) sem tocar no runtime: claude stop e claude rm nao acham o job',
|
|
169
|
+
});
|
|
170
|
+
itens.push({ ...base, acao: 'solto' });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { ok: inv.ok, dryRun: opcoes.dryRun === true, itens, falhas: inv.fontes.filter(f => !f.ok).map(f => `${f.origem}: ${f.detalhe}`) };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* RM-056 (ao fechar): as sessoes registradas na thread que o ledger dela ainda nao encerra. E o filtro
|
|
177
|
+
* barato do fechamento: so com alguma delas o `liberarAoFechar` consulta o inventario (o `claude agents`
|
|
178
|
+
* de cada conta). Sessao sem fim pode estar viva; quem decide se e fantasma e o inventario.
|
|
179
|
+
*/
|
|
180
|
+
function sessoesSemFim(raiz, threadId) {
|
|
181
|
+
const canonica = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
182
|
+
const t = (0, thread_1.lerThread)(canonica, threadId);
|
|
183
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(canonica, threadId));
|
|
184
|
+
const encerradas = new Set(eventos.filter(e => ENCERRAM_A_SESSAO.includes(e.tipo)).map(e => String(e.sessionId ?? '')));
|
|
185
|
+
const ids = (Array.isArray(t.sessoes) ? t.sessoes : []).map(s => s?.sessionId).filter((id) => typeof id === 'string');
|
|
186
|
+
return [...new Set(ids)].filter(id => !encerradas.has(id));
|
|
187
|
+
}
|
|
188
|
+
function textoDaLimpeza(r) {
|
|
189
|
+
if (r.itens.length === 0)
|
|
190
|
+
return `Nenhuma sessão fantasma${r.ok ? '' : ' nas contas que responderam'}.` +
|
|
191
|
+
r.falhas.map(f => `\nFalha: ${f}`).join('');
|
|
192
|
+
const verbo = {
|
|
193
|
+
solto: r.dryRun ? 'soltaria (sessao_morta no ledger)' : 'solto: sessao_morta no ledger',
|
|
194
|
+
'ja-solto': 'já encerrada no ledger', 'sem-thread': 'fora do ork: nada a soltar',
|
|
195
|
+
'outro-projeto': 'vínculo de outro projeto: rode lá',
|
|
196
|
+
};
|
|
197
|
+
return [
|
|
198
|
+
`Sessões fantasma (sem pid e sem processo)${r.dryRun ? ', ensaio: nada foi gravado' : ''}. O runtime não foi tocado.`,
|
|
199
|
+
(0, util_1.tabela)(['ID', 'PERFIL', 'THREAD/FASE', 'AÇÃO'], r.itens.map(i => [i.sessionId, i.perfil ?? 'processo',
|
|
200
|
+
i.thread ? `${i.thread}/${i.fase}` : '-', verbo[i.acao]])),
|
|
201
|
+
...r.falhas.map(f => `Falha: ${f}`),
|
|
202
|
+
].join('\n');
|
|
203
|
+
}
|