@orkastery/cli 0.6.0 → 0.6.1
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/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/dist/adapters/claude-bg.js +6 -6
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +612 -0
- package/dist/adapters/grok-mcp.js +178 -0
- package/dist/adapters/grok.js +686 -0
- package/dist/agendador-cpu.js +92 -0
- package/dist/branch-de-estado.js +7 -2
- package/dist/check-agil.js +48 -20
- package/dist/check-paralelo.js +73 -18
- package/dist/conducao.js +5 -0
- package/dist/docs.js +9 -16
- package/dist/doctor-forja.js +1 -3
- package/dist/doctor.js +1 -1
- package/dist/entrega-pr.js +10 -10
- package/dist/fabrica-estado.js +20 -11
- package/dist/horario.js +4 -1
- package/dist/index.js +20 -10
- package/dist/integracoes-locais.js +2 -0
- package/dist/locale-accounts.js +11 -3
- package/dist/locale-ajuda.js +10 -6
- package/dist/locale-forja.js +2 -4
- package/dist/locale-operacao-restante.js +2 -2
- package/dist/locale-retry.js +40 -2
- package/dist/locale-ship.js +10 -2
- package/dist/manifest.js +6 -1
- package/dist/mcp-install.js +22 -12
- package/dist/mcp-server.js +42 -16
- package/dist/merge-da-entrega.js +85 -0
- package/dist/network-roadmap.js +18 -14
- package/dist/noctum-aceite.js +154 -14
- package/dist/noctum-cli.js +69 -12
- package/dist/noctum-entrega.js +55 -7
- package/dist/noctum-envelope.js +1 -1
- package/dist/noctum-execucao.js +5 -1
- package/dist/noctum-fechamento.js +18 -7
- package/dist/noctum-fila.js +6 -1
- package/dist/noctum-guarda.js +81 -4
- package/dist/noctum-maquinas.js +5 -1
- package/dist/noctum-modo.js +35 -7
- package/dist/noctum-reconciliacao.js +4 -1
- package/dist/noctum-rede.js +102 -0
- package/dist/noctum-relatorio.js +44 -10
- package/dist/noctum-retomada-rede.js +71 -0
- package/dist/noctum-vigia.js +49 -9
- package/dist/parado-no-condutor.js +14 -15
- package/dist/phase.js +43 -15
- package/dist/postura-local.js +7 -3
- package/dist/preflight.js +2 -1
- package/dist/ratelimit.js +1 -0
- package/dist/retry.js +103 -10
- package/dist/runtime-context.js +165 -45
- package/dist/runtime-escada.js +7 -1
- package/dist/runtime-profiles.js +27 -17
- package/dist/runtimes.js +42 -5
- package/dist/security-scan/agil.js +18 -7
- package/dist/security-scan/executor.js +8 -0
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +19 -4
- package/dist/sessoes-contas.js +33 -5
- package/dist/setup.js +11 -11
- package/dist/varredura-acoes.js +8 -15
- package/dist/verify.js +26 -10
- package/package.json +2 -2
|
@@ -0,0 +1,425 @@
|
|
|
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.CONTRATO_CURSOR_GROK = exports.HEARTBEAT_GROK_MS = exports.AVISO_DA_RETOMADA_COM_MCP = void 0;
|
|
37
|
+
exports.baseDoArtefato = baseDoArtefato;
|
|
38
|
+
exports.fonteGrokDoDespacho = fonteGrokDoDespacho;
|
|
39
|
+
exports.validarFonteGrok = validarFonteGrok;
|
|
40
|
+
exports.fonteRegistradaGrok = fonteRegistradaGrok;
|
|
41
|
+
exports.resultadoDoDespacho = resultadoDoDespacho;
|
|
42
|
+
exports.classificarSessaoGrok = classificarSessaoGrok;
|
|
43
|
+
exports.observarGrokSobLock = observarGrokSobLock;
|
|
44
|
+
/**
|
|
45
|
+
* RM-067 (F2 da ork-grokbuild): conclusão de sessões `grok`. O fim vem do que o próprio runtime e o supervisor gravam
|
|
46
|
+
* (D9): o `end` do stream `streaming-json`, no log do supervisor, e o recibo do supervisor (`.exit.json`, com o código e
|
|
47
|
+
* o sinal do `close` real). A pasta da sessão (`<GROK_HOME>/sessions/<grupo>/<id>/updates.jsonl`) é prova de existência
|
|
48
|
+
* e de progresso, só pelo tamanho e pelo mtime: o `ork` nunca lê o conteúdo dela. O texto do agente nunca conclui
|
|
49
|
+
* nada, e o `end_turn` é necessário e nunca suficiente: a fase só conclui com a prova do `ork` (`provaDoOrk`, a mesma do
|
|
50
|
+
* claude-bg). Interrupção e fim por limite do runtime vão ao humano com a retomada (`-r <id>`), porque reexecutar a fase
|
|
51
|
+
* sobre a worktree já alterada é a armadilha que a D13 e a D17 da I-34 evitaram no claude-bg. O lock, a pasta de
|
|
52
|
+
* sensores e o processo destacado continuam no `session-watcher`; este módulo não o importa, para não criar ciclo.
|
|
53
|
+
*/
|
|
54
|
+
const fs = __importStar(require("node:fs"));
|
|
55
|
+
const path = __importStar(require("node:path"));
|
|
56
|
+
const grok_1 = require("./adapters/grok");
|
|
57
|
+
const grok_events_1 = require("./adapters/grok-events");
|
|
58
|
+
const codex_runner_1 = require("./adapters/codex-runner");
|
|
59
|
+
const session_events_1 = require("./session-events");
|
|
60
|
+
const ledger_1 = require("./ledger");
|
|
61
|
+
const thread_1 = require("./thread");
|
|
62
|
+
const session_watcher_claude_1 = require("./session-watcher-claude");
|
|
63
|
+
const doctor_pulse_cron_1 = require("./doctor-pulse-cron");
|
|
64
|
+
/**
|
|
65
|
+
* RM-067 (F4b, GO-FIX A3; F4c, D10): a retomada manual parte do `.grok/config.toml` da worktree como ele está, e a sessão
|
|
66
|
+
* podia escrever nele no perfil `workspace`. Desde a F4c, o servidor do `ork` morre com a sessão, e o token dele só existia
|
|
67
|
+
* no ambiente dela (`bearer_token_env_var`): a retomada manual não alcança o MCP do `ork`, nem o servidor de outra sessão que
|
|
68
|
+
* pegue a mesma porta, que recusa sem o token dela; o `ork retry` sobe outro servidor e reescreve a config antes de redespachar.
|
|
69
|
+
*/
|
|
70
|
+
exports.AVISO_DA_RETOMADA_COM_MCP = 'o servidor MCP do ork terminou com a sessao, e o token dele so existia no ambiente ' +
|
|
71
|
+
'dela, entao a retomada manual roda sem o MCP do ork e parte do .grok/config.toml da worktree como a sessao o deixou: para ' +
|
|
72
|
+
'retomar com o MCP, redespache pelo ork retry, que sobe outro servidor e reescreve a config';
|
|
73
|
+
/** D12: no máximo um `runtime_heartbeat` por minuto; o stream do `grok` emite um evento por pedaço de texto. */
|
|
74
|
+
exports.HEARTBEAT_GROK_MS = 60_000;
|
|
75
|
+
exports.CONTRATO_CURSOR_GROK = 'ork.session-cursor-grok/v1';
|
|
76
|
+
/** Bloco lido por observação: uma linha no limite e folga para as seguintes. */
|
|
77
|
+
const BLOCO_GROK_BYTES = grok_events_1.LIMITE_LINHA_GROK + 65536;
|
|
78
|
+
const LIMITE_METADADO_BYTES = 65536;
|
|
79
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
80
|
+
const SHA_COMMIT = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
81
|
+
/**
|
|
82
|
+
* D8: a base do artefato da fase, lida ANTES do despacho, como o HEAD do GO. O despacho do grok espera a pasta da
|
|
83
|
+
* sessão, e a sessão pode gravar o documento antes do registro da fonte: a base lida depois seria o próprio documento.
|
|
84
|
+
* `null` quando a fase não entrega artefato.
|
|
85
|
+
*/
|
|
86
|
+
function baseDoArtefato(raiz, threadId, fase) {
|
|
87
|
+
const arquivo = session_watcher_claude_1.ARTEFATO_DA_FASE[fase];
|
|
88
|
+
return arquivo ? { arquivo, sha256: (0, session_watcher_claude_1.estadoDoArtefato)(raiz, threadId, arquivo).sha256 } : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* D8: a fonte do despacho, com a base do artefato e o HEAD do GO lidos antes dele (`noDespacho`); sem a base, ela é
|
|
92
|
+
* lida agora, como no claude-bg.
|
|
93
|
+
*/
|
|
94
|
+
function fonteGrokDoDespacho(raiz, threadId, fase, cwd, sensores, grokHome, noDespacho = {}) {
|
|
95
|
+
if (!path.isAbsolute(cwd))
|
|
96
|
+
throw new Error('despacho grok sem cwd absoluto');
|
|
97
|
+
const artefato = noDespacho.artefato !== undefined ? noDespacho.artefato : baseDoArtefato(raiz, threadId, fase);
|
|
98
|
+
return { grok: 'streaming-json', logPath: sensores.logPath, processoPath: sensores.processoPath, reciboPath: sensores.reciboPath,
|
|
99
|
+
cwd, grokHome,
|
|
100
|
+
...(artefato ? { artefato } : {}),
|
|
101
|
+
...(fase === 'GO' ? { head: noDespacho.head ?? null } : {}) };
|
|
102
|
+
}
|
|
103
|
+
/** D8: confere a fonte do `grok` contra o despacho e o diretório de sessões da thread. */
|
|
104
|
+
function validarFonteGrok(eventos, sessionId, registro, dirDaThread) {
|
|
105
|
+
if (registro.grok !== 'streaming-json' || typeof registro.cwd !== 'string' || !path.isAbsolute(registro.cwd) ||
|
|
106
|
+
typeof registro.grokHome !== 'string' || !path.isAbsolute(registro.grokHome))
|
|
107
|
+
throw new Error('registro de sensor do grok inválido');
|
|
108
|
+
const sessoes = path.join(dirDaThread, 'sessoes');
|
|
109
|
+
if (typeof registro.logPath !== 'string' || registro.logPath !== path.join(sessoes, `${sessionId}.grok.jsonl`) ||
|
|
110
|
+
registro.processoPath !== `${registro.logPath}.process.json` || registro.reciboPath !== `${registro.logPath}.exit.json`)
|
|
111
|
+
throw new Error('fonte de sensor do grok fora do diretório canônico de sessões');
|
|
112
|
+
const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessionId).at(-1);
|
|
113
|
+
if (despacho?.cwd !== undefined && despacho.cwd !== registro.cwd)
|
|
114
|
+
throw new Error('cwd registrado divergente do despacho');
|
|
115
|
+
if (registro.artefato !== undefined && (!Object.values(session_watcher_claude_1.ARTEFATO_DA_FASE).includes(registro.artefato.arquivo) ||
|
|
116
|
+
(typeof despacho?.fase === 'string' && session_watcher_claude_1.ARTEFATO_DA_FASE[despacho.fase] !== registro.artefato.arquivo) ||
|
|
117
|
+
(registro.artefato.sha256 !== null && !SHA256.test(String(registro.artefato.sha256)))))
|
|
118
|
+
throw new Error('base de artefato do registro do grok inválida');
|
|
119
|
+
if (registro.head !== undefined && ((registro.head !== null && !SHA_COMMIT.test(String(registro.head))) ||
|
|
120
|
+
(typeof despacho?.fase === 'string' && despacho.fase !== 'GO')))
|
|
121
|
+
throw new Error('HEAD de despacho do registro do grok inválido');
|
|
122
|
+
}
|
|
123
|
+
/** A fonte do `grok` registrada para este despacho, se houver. Leitura pura do ledger. */
|
|
124
|
+
function fonteRegistradaGrok(eventos, sessao) {
|
|
125
|
+
const r = eventos.filter(e => e.tipo === 'session_sensor_registered' && e.sessionId === sessao.sessionId &&
|
|
126
|
+
e.despachoEm === sessao.despachadaEm).at(-1);
|
|
127
|
+
return r?.grok === 'streaming-json' ? r : undefined;
|
|
128
|
+
}
|
|
129
|
+
/** D10: o resultado já gravado para este despacho, de qualquer fonte (o observador ou a batida). */
|
|
130
|
+
function resultadoDoDespacho(eventos, sessao) {
|
|
131
|
+
return eventos.find(e => e.tipo === 'phase_result' && e.sessionId === sessao.sessionId &&
|
|
132
|
+
(e.despachoEm === undefined || e.despachoEm === sessao.despachadaEm));
|
|
133
|
+
}
|
|
134
|
+
const RETOMADA = 'o estado da sessão ficou salvo até a última ferramenta concluída e os arquivos alterados ficam como estão: ' +
|
|
135
|
+
'retome a sessão com o comando da retomada ou redespache a fase';
|
|
136
|
+
/**
|
|
137
|
+
* D9: a tabela de classificação, pura sobre as entradas. A ordem importa: sem recibo, o processo vivo espera; o recibo
|
|
138
|
+
* de interrupção vem antes do código; a saída 0 só conclui pelo `end`; a outra saída só é tipada pelo `error` do runtime.
|
|
139
|
+
* GO-FIX 1 (A5): com outra saída e sem `error`, o `end` válido vai ao humano, e o `cancelled` vale como interrupção: o
|
|
140
|
+
* turno parou, e o `runtime.unavailable` que a batida reexecuta refaria a fase sobre a worktree já alterada.
|
|
141
|
+
*/
|
|
142
|
+
function classificarSessaoGrok(e) {
|
|
143
|
+
const { estado, recibo } = e;
|
|
144
|
+
const bloqueio = (motivo, fonte, extra = {}) => ({ terminal: true, classificacao: 'gate_blocked', motivo, fonte, conclusaoNativa: false, diagnostico: fonte, ...extra });
|
|
145
|
+
const interrupcao = (codigo, sinal, fonte) => bloqueio('human.pending', fonte, { interrupcao: { codigo, sinal, stopReason: estado.fim?.stopReason ?? null },
|
|
146
|
+
retomar: true, diagnostico: `${fonte}; ${RETOMADA}` });
|
|
147
|
+
const pelaFim = (comRecibo) => {
|
|
148
|
+
const fim = estado.fim;
|
|
149
|
+
if (fim.stopReason === grok_events_1.STOP_CANCELADO)
|
|
150
|
+
return interrupcao(recibo?.exitCode ?? null, recibo?.signal ?? null, 'end com stopReason cancelled');
|
|
151
|
+
if (fim.stopReason !== grok_events_1.STOP_FIM_NORMAL) {
|
|
152
|
+
return bloqueio('human.pending', `end com stopReason ${fim.stopReason}`, { retomar: true,
|
|
153
|
+
diagnostico: `o grok encerrou o turno por ${fim.stopReason}, sem o fim normal; ${RETOMADA}` });
|
|
154
|
+
}
|
|
155
|
+
const sinal = comRecibo ? 'end com stopReason end_turn e saida 0 no recibo do supervisor'
|
|
156
|
+
: 'end com stopReason end_turn e processo encerrado sem o recibo do supervisor';
|
|
157
|
+
const prova = e.prova();
|
|
158
|
+
if (!prova.ok) {
|
|
159
|
+
return bloqueio(prova.motivo ?? 'human.pending', `${sinal}; sem prova do ork: ${prova.fonte}`, { conclusaoNativa: comRecibo, diagnostico: `sem prova do ork: ${prova.fonte}` });
|
|
160
|
+
}
|
|
161
|
+
if (e.pausaAoFim) {
|
|
162
|
+
return bloqueio('human.pending', `${sinal}; ${prova.fonte}; pausa prevista ao fim do bloco`, { conclusaoNativa: comRecibo, diagnostico: null });
|
|
163
|
+
}
|
|
164
|
+
return { terminal: true, classificacao: 'fase_concluida', motivo: null, fonte: `${sinal}; ${prova.fonte}`,
|
|
165
|
+
conclusaoNativa: comRecibo, diagnostico: null };
|
|
166
|
+
};
|
|
167
|
+
const valido = !estado.invalido && !estado.sessaoDivergente;
|
|
168
|
+
if (!recibo) {
|
|
169
|
+
if (e.filho === 'vivo')
|
|
170
|
+
return { terminal: false, espera: 'sessão trabalhando' };
|
|
171
|
+
if (e.supervisor === 'vivo')
|
|
172
|
+
return { terminal: false, espera: 'processo do grok encerrado; aguardando o recibo do supervisor' };
|
|
173
|
+
if (estado.fim && valido && e.filho === 'ausente')
|
|
174
|
+
return pelaFim(false);
|
|
175
|
+
if (e.agoraMs - e.progressoEm >= e.limiteMs) {
|
|
176
|
+
return bloqueio('runtime.unavailable', estado.fim ? 'end sem recibo do supervisor e processo sem prova de vida além do limite'
|
|
177
|
+
: 'stream sem o evento end, processo sem prova de vida e sem progresso além do limite de estabilidade', { morte: true });
|
|
178
|
+
}
|
|
179
|
+
return { terminal: false, espera: 'processo sem prova de vida e sem recibo; aguardando o limite de estabilidade' };
|
|
180
|
+
}
|
|
181
|
+
if (recibo.erro)
|
|
182
|
+
return bloqueio('runtime.unavailable', `o supervisor não iniciou o grok (${recibo.erro})`);
|
|
183
|
+
if (recibo.exitCode === 130 || recibo.exitCode === 143 || recibo.signal === 'SIGINT' || recibo.signal === 'SIGTERM') {
|
|
184
|
+
return interrupcao(recibo.exitCode, recibo.signal, `processo interrompido (${recibo.signal ?? `saida ${recibo.exitCode}`}) no recibo do supervisor`);
|
|
185
|
+
}
|
|
186
|
+
if (recibo.exitCode === 0) {
|
|
187
|
+
if (estado.sessaoDivergente)
|
|
188
|
+
return bloqueio('runtime.unavailable', 'saida 0 com o end sem o id da sessão despachada');
|
|
189
|
+
if (estado.invalido)
|
|
190
|
+
return bloqueio('runtime.unavailable', 'saida 0 com linha inválida no stream');
|
|
191
|
+
if (!estado.fim)
|
|
192
|
+
return bloqueio('runtime.unavailable', 'saida 0 sem o evento end no stream');
|
|
193
|
+
return pelaFim(true);
|
|
194
|
+
}
|
|
195
|
+
const codigo = recibo.exitCode ?? recibo.signal ?? 'sem codigo';
|
|
196
|
+
if (estado.erro) {
|
|
197
|
+
const conta = (0, grok_events_1.falhaDoErroGrok)(estado.erro, e.agoraMs);
|
|
198
|
+
return bloqueio(conta?.motivo ?? 'runtime.unavailable', `saida ${codigo} com o objeto error do runtime: ${estado.erro.mensagem}`, { erroDoRuntime: estado.erro.mensagem, ...(conta ? { falhaDeConta: conta } : {}) });
|
|
199
|
+
}
|
|
200
|
+
if (estado.fim && valido) {
|
|
201
|
+
if (estado.fim.stopReason === grok_events_1.STOP_CANCELADO) {
|
|
202
|
+
return interrupcao(recibo.exitCode, recibo.signal, `end com stopReason cancelled e saida ${codigo}`);
|
|
203
|
+
}
|
|
204
|
+
return bloqueio('human.pending', `saida ${codigo} depois do end com stopReason ${estado.fim.stopReason}, sem o objeto error`, { retomar: true, diagnostico: `o grok saiu ${codigo} depois de encerrar o turno (${estado.fim.stopReason}), sem o objeto error: ` +
|
|
205
|
+
`a fase pode ter terminado; ${RETOMADA}` });
|
|
206
|
+
}
|
|
207
|
+
return bloqueio('runtime.unavailable', `saida ${codigo} sem o objeto error no stream`);
|
|
208
|
+
}
|
|
209
|
+
function lerJson(arquivo) {
|
|
210
|
+
let fd;
|
|
211
|
+
try {
|
|
212
|
+
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
if (e.code === 'ENOENT')
|
|
216
|
+
return null;
|
|
217
|
+
throw e;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const st = fs.fstatSync(fd);
|
|
221
|
+
if (!st.isFile() || st.size > LIMITE_METADADO_BYTES)
|
|
222
|
+
throw new Error('metadado de sensor excessivo');
|
|
223
|
+
const bytes = Buffer.alloc(st.size);
|
|
224
|
+
fs.readSync(fd, bytes, 0, st.size, 0);
|
|
225
|
+
return JSON.parse(bytes.toString('utf8'));
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
fs.closeSync(fd);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* O recibo do supervisor só vale inteiro e do mesmo processo do `.process.json`, como no codex. GO-FIX 1: o código
|
|
233
|
+
* negativo só vale com o erro de spawn, que o Node entrega como o errno negativo no `close` (`-2` com `ENOENT`).
|
|
234
|
+
*/
|
|
235
|
+
function conferirRecibo(recibo, processo, agoraMs) {
|
|
236
|
+
const erroDeSpawn = typeof recibo.erro === 'string' && recibo.erro !== '';
|
|
237
|
+
if (!processo || recibo.schema !== 'ork.codex-process/v1' || recibo.iniciadoEm !== processo.iniciadoEm ||
|
|
238
|
+
JSON.stringify(recibo.filho) !== JSON.stringify(processo.filho) || !Number.isFinite(Date.parse(recibo.terminadoEm)) ||
|
|
239
|
+
Date.parse(recibo.terminadoEm) > agoraMs || !Number.isFinite(recibo.duracaoMs) || recibo.duracaoMs < 0 ||
|
|
240
|
+
(recibo.exitCode !== null && (!Number.isInteger(recibo.exitCode) || (recibo.exitCode < 0 && !erroDeSpawn))) ||
|
|
241
|
+
(recibo.signal !== null && typeof recibo.signal !== 'string') ||
|
|
242
|
+
(recibo.erro !== null && typeof recibo.erro !== 'string'))
|
|
243
|
+
throw new Error('recibo de processo inválido');
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Lê do log do stream o que cresceu desde o cursor, só linhas inteiras. O supervisor abre o log com `O_EXCL` e ele só
|
|
247
|
+
* cresce: trocado ou encurtado é corrupção, e o stream fica inválido.
|
|
248
|
+
*/
|
|
249
|
+
function lerStream(arquivo, cursor, sessionId) {
|
|
250
|
+
let fd;
|
|
251
|
+
try {
|
|
252
|
+
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
if (e.code === 'ENOENT')
|
|
256
|
+
return { bytes: 0, eventos: 0, backlog: false, parcial: false, mtimeMs: null };
|
|
257
|
+
throw e;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const st = fs.fstatSync(fd);
|
|
261
|
+
if (!st.isFile())
|
|
262
|
+
throw new Error('runtime.unavailable: stream do grok não é arquivo comum');
|
|
263
|
+
if ((cursor.ino !== null && cursor.ino !== st.ino) || st.size < cursor.offset) {
|
|
264
|
+
cursor.parser = { ...cursor.parser, invalido: true };
|
|
265
|
+
cursor.ino = st.ino;
|
|
266
|
+
cursor.offset = st.size;
|
|
267
|
+
return { bytes: 0, eventos: 0, backlog: false, parcial: false, mtimeMs: st.mtimeMs };
|
|
268
|
+
}
|
|
269
|
+
cursor.ino = st.ino;
|
|
270
|
+
const n = Math.min(BLOCO_GROK_BYTES, st.size - cursor.offset);
|
|
271
|
+
const buf = Buffer.alloc(n);
|
|
272
|
+
const lidos = fs.readSync(fd, buf, 0, n, cursor.offset);
|
|
273
|
+
const parser = new grok_events_1.ParserGrok(sessionId, cursor.parser);
|
|
274
|
+
const fim = buf.subarray(0, lidos).lastIndexOf(10);
|
|
275
|
+
if (fim >= 0) {
|
|
276
|
+
parser.push(buf.subarray(0, fim + 1));
|
|
277
|
+
cursor.offset += fim + 1;
|
|
278
|
+
}
|
|
279
|
+
else if (lidos >= grok_events_1.LIMITE_LINHA_GROK) {
|
|
280
|
+
parser.estado.invalido = true;
|
|
281
|
+
cursor.offset += lidos;
|
|
282
|
+
}
|
|
283
|
+
cursor.parser = parser.estado;
|
|
284
|
+
const resta = st.size > cursor.offset;
|
|
285
|
+
return { bytes: lidos, eventos: parser.eventosValidos, backlog: resta && lidos === BLOCO_GROK_BYTES,
|
|
286
|
+
parcial: resta && lidos < BLOCO_GROK_BYTES, mtimeMs: st.mtimeMs };
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
fs.closeSync(fd);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const tokensIndisponiveis = () => ({ disponivel: false, input: null, cachedInput: null, output: null,
|
|
293
|
+
total: null, unidade: 'tokens', escopo: 'sessao', fonte: 'unavailable' });
|
|
294
|
+
/** Corpo da observação do `grok`, chamado pelo `session-watcher` já dentro do lock do watcher. */
|
|
295
|
+
function observarGrokSobLock(ctx) {
|
|
296
|
+
const { raiz, thread, sessao, dir, agoraMs } = ctx;
|
|
297
|
+
const base = { sessionId: sessao.sessionId, thread: thread.id, concluido: false, bytesLidos: 0 };
|
|
298
|
+
const resultadoId = `grok:${ctx.chave}`;
|
|
299
|
+
const gravar = (tipo, dados) => {
|
|
300
|
+
if (!(0, ledger_1.registrarSeExiste)(dir, thread.id, tipo, dados))
|
|
301
|
+
throw new Error(session_watcher_claude_1.THREAD_REMOVIDA);
|
|
302
|
+
};
|
|
303
|
+
const eventos = (0, ledger_1.lerLedger)(dir);
|
|
304
|
+
const pronto = resultadoDoDespacho(eventos, sessao);
|
|
305
|
+
if (pronto)
|
|
306
|
+
return { ...base, concluido: true, classificacao: String(pronto.classificacao) };
|
|
307
|
+
const fonte = fonteRegistradaGrok(eventos, sessao);
|
|
308
|
+
if (!fonte)
|
|
309
|
+
throw new Error('fonte de sensor do grok ausente');
|
|
310
|
+
validarFonteGrok(eventos, sessao.sessionId, fonte, dir);
|
|
311
|
+
const arquivoCursor = path.join(ctx.pasta, 'cursor-grok.json');
|
|
312
|
+
const lido = lerJson(arquivoCursor);
|
|
313
|
+
if (lido && (lido.schema !== exports.CONTRATO_CURSOR_GROK || lido.sessionId !== sessao.sessionId || lido.despachoEm !== sessao.despachadaEm))
|
|
314
|
+
throw new Error('cursor de outra sessão/despacho');
|
|
315
|
+
const cursor = lido ?? { schema: exports.CONTRATO_CURSOR_GROK, sessionId: sessao.sessionId, despachoEm: sessao.despachadaEm,
|
|
316
|
+
ino: null, offset: 0, parser: new grok_events_1.ParserGrok(sessao.sessionId).estado, progressoEm: Date.parse(sessao.despachadaEm),
|
|
317
|
+
heartbeatEm: 0, updatesBytes: null };
|
|
318
|
+
// A ordem das leituras é a do supervisor ao contrário: ele grava o recibo depois do `close` do filho e só então sai.
|
|
319
|
+
// GO-FIX 2 (N1): a vida antes do recibo; o supervisor visto ausente já deixou no disco o recibo que gravaria.
|
|
320
|
+
// GO-FIX 1 (A2): o recibo antes do stream; com o recibo no disco, o stream lido em seguida está inteiro.
|
|
321
|
+
const processo = lerJson(fonte.processoPath);
|
|
322
|
+
const vida = { filho: (0, codex_runner_1.estadoProcesso)(processo?.filho ?? null), supervisor: (0, codex_runner_1.estadoProcesso)(processo?.supervisor ?? null) };
|
|
323
|
+
const recibo = lerJson(fonte.reciboPath);
|
|
324
|
+
if (recibo)
|
|
325
|
+
conferirRecibo(recibo, processo, ctx.relogioReal ? Date.now() : agoraMs);
|
|
326
|
+
const leitura = lerStream(fonte.logPath, cursor, sessao.sessionId);
|
|
327
|
+
let progrediu = false;
|
|
328
|
+
if (leitura.eventos > 0 && leitura.mtimeMs !== null) {
|
|
329
|
+
progrediu = true;
|
|
330
|
+
cursor.progressoEm = Math.max(cursor.progressoEm, Math.min(agoraMs, leitura.mtimeMs));
|
|
331
|
+
}
|
|
332
|
+
// O processo terminou e a última linha não terminou: ela nunca vai se completar.
|
|
333
|
+
if (recibo && leitura.parcial)
|
|
334
|
+
cursor.parser = { ...cursor.parser, invalido: true };
|
|
335
|
+
// A pasta da sessão é do runtime: só o tamanho do `updates.jsonl` conta como progresso, nunca o conteúdo.
|
|
336
|
+
const emDisco = (0, grok_1.acharSessaoGrok)(sessao.sessionId, fonte.grokHome);
|
|
337
|
+
if (emDisco && (cursor.updatesBytes === null || emDisco.tamanho > cursor.updatesBytes)) {
|
|
338
|
+
progrediu = true;
|
|
339
|
+
cursor.progressoEm = Math.max(cursor.progressoEm, Math.min(agoraMs, emDisco.mtimeMs));
|
|
340
|
+
}
|
|
341
|
+
if (emDisco)
|
|
342
|
+
cursor.updatesBytes = emDisco.tamanho;
|
|
343
|
+
let prova;
|
|
344
|
+
const c = leitura.backlog ? { terminal: false, espera: 'stream com bytes ainda não lidos' }
|
|
345
|
+
: classificarSessaoGrok({ estado: cursor.parser,
|
|
346
|
+
recibo: recibo ? { exitCode: recibo.exitCode, signal: recibo.signal, erro: recibo.erro, duracaoMs: recibo.duracaoMs } : null,
|
|
347
|
+
filho: recibo ? 'ausente' : vida.filho,
|
|
348
|
+
supervisor: recibo ? 'ausente' : vida.supervisor,
|
|
349
|
+
progressoEm: cursor.progressoEm, agoraMs, limiteMs: ctx.limiteMs, pausaAoFim: (0, thread_1.pausaNaThread)(thread, sessao.fase),
|
|
350
|
+
prova: () => prova ??= (0, session_watcher_claude_1.provaDoOrk)(raiz, thread.id, sessao, eventos, fonte) });
|
|
351
|
+
(0, codex_runner_1.gravarAtomico)(arquivoCursor, cursor);
|
|
352
|
+
if (!(0, ledger_1.threadPresente)(dir)) {
|
|
353
|
+
fs.rmSync(arquivoCursor, { force: true });
|
|
354
|
+
throw new Error(session_watcher_claude_1.THREAD_REMOVIDA);
|
|
355
|
+
}
|
|
356
|
+
return (0, session_events_1.comLockDeSessao)(dir, () => {
|
|
357
|
+
const atuais = (0, ledger_1.lerLedger)(dir);
|
|
358
|
+
if (progrediu && (cursor.heartbeatEm === 0 || cursor.progressoEm - cursor.heartbeatEm >= exports.HEARTBEAT_GROK_MS)) {
|
|
359
|
+
const heartbeatId = `${resultadoId}:heartbeat:${cursor.progressoEm}`;
|
|
360
|
+
if (!atuais.some(e => e.sensorEventId === heartbeatId))
|
|
361
|
+
gravar('runtime_heartbeat', {
|
|
362
|
+
ts: new Date(cursor.progressoEm).toISOString(), fase: sessao.fase, sessionId: sessao.sessionId,
|
|
363
|
+
despachoEm: sessao.despachadaEm, sensorEventId: heartbeatId, runtime: 'grok',
|
|
364
|
+
fonte: 'progresso do stream ou do updates.jsonl do grok', bytesLidos: leitura.bytes
|
|
365
|
+
});
|
|
366
|
+
cursor.heartbeatEm = cursor.progressoEm;
|
|
367
|
+
(0, codex_runner_1.gravarAtomico)(arquivoCursor, cursor);
|
|
368
|
+
}
|
|
369
|
+
if (!c.terminal)
|
|
370
|
+
return { ...base, bytesLidos: leitura.bytes, espera: c.espera };
|
|
371
|
+
const ja = resultadoDoDespacho(atuais, sessao);
|
|
372
|
+
if (ja)
|
|
373
|
+
return { ...base, bytesLidos: leitura.bytes, concluido: true, classificacao: String(ja.classificacao) };
|
|
374
|
+
// RM-067 (F4b, G8 e D6): com o MCP, a sessão grava commit, artefato e verify pelo núcleo até o fim, e o ledger da prova
|
|
375
|
+
// foi lido antes do recibo. A prova é refeita sobre o ledger relido sob o lock; se mudou, a decisão fica para a próxima
|
|
376
|
+
// volta, como a do Stop no claude-bg. Converge: a prova só muda com evento novo do intervalo. GO-FIX A1: a prova inteira
|
|
377
|
+
// (o `verify` do CHECK, os commits do GO, o evento do SHIP e do MASTER), não só o texto, porque o `phase_result` a grava.
|
|
378
|
+
if (prova !== undefined && JSON.stringify((0, session_watcher_claude_1.provaDoOrk)(raiz, thread.id, sessao, atuais, fonte)) !== JSON.stringify(prova))
|
|
379
|
+
return { ...base, bytesLidos: leitura.bytes, espera: 'evento do núcleo gravado durante a observação; reavaliar na próxima volta' };
|
|
380
|
+
const despacho = atuais.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessao.sessionId).at(-1);
|
|
381
|
+
const comando = Array.isArray(despacho?.comando) && despacho.comando.every(a => typeof a === 'string') ? despacho.comando : [];
|
|
382
|
+
const retomada = c.retomar ? (0, grok_1.montarComandoDeRetomada)(comando, sessao.sessionId) : null;
|
|
383
|
+
const artefato = (0, session_watcher_claude_1.saidaDoArtefato)(raiz, thread.id, sessao, fonte, eventos);
|
|
384
|
+
const fim = cursor.parser.fim;
|
|
385
|
+
// GO-FIX 1 (S4): o comando da retomada vai pronto para colar, com aspas no argumento que precisa delas. RM-067 (F4b, D5):
|
|
386
|
+
// com as variáveis do filho na frente, para a sessão retomada também não carregar o MCP de outros clientes. GO-FIX A3: com
|
|
387
|
+
// o MCP do ork, a retomada parte da config que a sessão podia escrever, e o detalhe diz isso.
|
|
388
|
+
const ambiente = Object.entries(grok_1.AMBIENTE_SEM_MCP_DE_OUTROS_CLIENTES).map(([nome, valor]) => `${nome}=${valor}`);
|
|
389
|
+
// GO-FIX N4: o aviso vem antes do comando, que pode levar uma dezena de regras e as telas cortam o detalhe no fim.
|
|
390
|
+
const comMcp = retomada?.some((a) => a.startsWith('MCPTool(orkastery__')) ? `; ${exports.AVISO_DA_RETOMADA_COM_MCP}` : '';
|
|
391
|
+
const detalhe = c.diagnostico === null ? null
|
|
392
|
+
: retomada ? `${c.diagnostico}${comMcp}; retomada: ${[...ambiente, ...retomada.map(doctor_pulse_cron_1.citarNoShell)].join(' ')}` : c.diagnostico;
|
|
393
|
+
const dados = { fase: sessao.fase, sessionId: sessao.sessionId, despachoEm: sessao.despachadaEm, sensorResultId: resultadoId,
|
|
394
|
+
classificacao: c.classificacao, motivo: c.motivo, runtime: 'grok', fonte: c.fonte,
|
|
395
|
+
stopReason: fim?.stopReason ?? null, numTurnos: fim?.numTurnos ?? null, tokens: fim?.tokens ?? tokensIndisponiveis(),
|
|
396
|
+
custoUsd: fim?.custoUsd ?? null,
|
|
397
|
+
exitCode: recibo?.exitCode ?? null, exitCodeFonte: recibo ? 'supervisor.close' : 'unavailable', signal: recibo?.signal ?? null,
|
|
398
|
+
duracaoMs: recibo?.duracaoMs ?? null, duracaoFonte: recibo ? 'supervisor.close' : 'unavailable',
|
|
399
|
+
ok: c.classificacao === 'fase_concluida' && recibo?.exitCode === 0,
|
|
400
|
+
estado: c.classificacao === 'fase_concluida' ? 'concluida' : 'bloqueada',
|
|
401
|
+
conclusaoNativa: c.conclusaoNativa, conclusaoNativaAusente: c.conclusaoNativa ? null : c.fonte,
|
|
402
|
+
...(artefato ? { artefato: { arquivo: artefato.arquivo, sha256Base: artefato.sha256Base, sha256Atual: artefato.sha256 } } : {}),
|
|
403
|
+
...(prova ? { provaOrk: prova } : {}),
|
|
404
|
+
...(c.interrupcao ? { interrupcao: c.interrupcao } : {}),
|
|
405
|
+
...(c.retomar ? { retomada: retomada ? { sessionId: sessao.sessionId, comando: retomada } : null } : {}),
|
|
406
|
+
...(c.erroDoRuntime ? { erroDoRuntime: c.erroDoRuntime } : {}),
|
|
407
|
+
...(c.falhaDeConta ? { falhaDeConta: { motivo: c.falhaDeConta.motivo, resetEm: c.falhaDeConta.resetEm,
|
|
408
|
+
fonte: c.falhaDeConta.fonte, trecho: c.falhaDeConta.trecho } } : {}),
|
|
409
|
+
// D18 da I-34: o diagnóstico vai ao pedido HITL e, como `detalhe` do `gate_blocked`, ao monitor e ao pulse.
|
|
410
|
+
diagnostico: c.diagnostico, ...(detalhe ? { detalhe } : {}),
|
|
411
|
+
observadoEm: new Date(agoraMs).toISOString(), gate: 'phase.dispatch', origem: 'sessions.watch',
|
|
412
|
+
morteLatenciaMs: c.morte ? agoraMs - cursor.progressoEm : null };
|
|
413
|
+
// GO-FIX 1 (S1): cada evento do fim uma vez por despacho, como nos outros observadores; o processo que morre entre as
|
|
414
|
+
// gravações não repete o `gate_blocked` na volta seguinte.
|
|
415
|
+
const gravarUmaVez = (tipo) => {
|
|
416
|
+
if (!atuais.some(e => e.tipo === tipo && e.sensorResultId === resultadoId))
|
|
417
|
+
gravar(tipo, dados);
|
|
418
|
+
};
|
|
419
|
+
if (c.morte)
|
|
420
|
+
gravarUmaVez('sessao_morta');
|
|
421
|
+
gravarUmaVez(c.classificacao);
|
|
422
|
+
gravar('phase_result', dados);
|
|
423
|
+
return { ...base, bytesLidos: leitura.bytes, concluido: true, classificacao: c.classificacao };
|
|
424
|
+
});
|
|
425
|
+
}
|
package/dist/session-watcher.js
CHANGED
|
@@ -58,6 +58,7 @@ const codex_controller_sensor_1 = require("./adapters/codex-controller-sensor");
|
|
|
58
58
|
const retry_1 = require("./retry");
|
|
59
59
|
const ratelimit_1 = require("./ratelimit");
|
|
60
60
|
const session_watcher_claude_1 = require("./session-watcher-claude");
|
|
61
|
+
const session_watcher_grok_1 = require("./session-watcher-grok");
|
|
61
62
|
const conta_esgotada_1 = require("./conta-esgotada");
|
|
62
63
|
const trabalho_thread_1 = require("./trabalho-thread");
|
|
63
64
|
/** Falhas operacionais e bloqueios sem retry automático não alegam conclusão do produto. */
|
|
@@ -286,7 +287,7 @@ function conferirRegistro(dir, registro) {
|
|
|
286
287
|
* com o dono da remoção.
|
|
287
288
|
*/
|
|
288
289
|
function desfazerNaThreadRemovida(pasta, criadas) {
|
|
289
|
-
for (const cursor of ['cursor.json', 'cursor-claude.json']) {
|
|
290
|
+
for (const cursor of ['cursor.json', 'cursor-claude.json', 'cursor-grok.json']) {
|
|
290
291
|
try {
|
|
291
292
|
fs.rmSync(path.join(pasta, cursor), { force: true });
|
|
292
293
|
}
|
|
@@ -393,7 +394,7 @@ function reconciliarSessaoSemWatcher(carregado, threadId, agoraMs = Date.now())
|
|
|
393
394
|
const ignorada = (detalhe) => ({ thread: threadId,
|
|
394
395
|
sessionId: sessao?.sessionId ?? null, estado: 'ignorada', detalhe });
|
|
395
396
|
if (thread.status === 'fechada' || !sessao || sessao.verificada !== true ||
|
|
396
|
-
!['codex', 'claude-bg'].includes(sessao.runtime) || typeof sessao.despachadaEm !== 'string')
|
|
397
|
+
!['codex', 'claude-bg', 'grok'].includes(sessao.runtime) || typeof sessao.despachadaEm !== 'string')
|
|
397
398
|
return ignorada('sem sessão despachada e verificada que o watcher possa reconciliar');
|
|
398
399
|
const dir = (0, thread_1.dirThread)(carregado.raiz, threadId);
|
|
399
400
|
let eventos = (0, ledger_1.lerLedger)(dir);
|
|
@@ -489,8 +490,8 @@ function reconciliarSessaoSemWatcher(carregado, threadId, agoraMs = Date.now())
|
|
|
489
490
|
}
|
|
490
491
|
function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registrada }, opcoes) {
|
|
491
492
|
const sessao = (0, session_events_1.exigirSessaoDespachada)(registrada, sessionId);
|
|
492
|
-
if (sessao.runtime !== 'codex' && sessao.runtime !== 'claude-bg')
|
|
493
|
-
throw new Error('watch requer sessão Codex ou claude-bg registrada');
|
|
493
|
+
if (sessao.runtime !== 'codex' && sessao.runtime !== 'claude-bg' && sessao.runtime !== 'grok')
|
|
494
|
+
throw new Error('watch requer sessão Codex ou claude-bg ou grok registrada');
|
|
494
495
|
const now = opcoes.agoraMs ?? Date.now();
|
|
495
496
|
if (!Number.isFinite(now))
|
|
496
497
|
throw new Error('relógio inválido');
|
|
@@ -526,6 +527,12 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
526
527
|
thread, sessao, dir, pasta, chave, agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, consulta: opcoes.consultaClaude, carregado })) ?? { ...base, ocupado: true };
|
|
527
528
|
return (0, ledger_1.threadPresente)(dir) ? r : desfazer();
|
|
528
529
|
}
|
|
530
|
+
// RM-067 (F2): o grok tem observador próprio (stream, recibo do supervisor e pasta da sessão), no mesmo lock.
|
|
531
|
+
if (sessao.runtime === 'grok') {
|
|
532
|
+
const r = comLockWatcher(pasta, () => (0, session_watcher_grok_1.observarGrokSobLock)({ raiz: carregado.raiz, thread, sessao, dir, pasta, chave,
|
|
533
|
+
agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, relogioReal: opcoes.agoraMs === undefined })) ?? { ...base, ocupado: true };
|
|
534
|
+
return (0, ledger_1.threadPresente)(dir) ? r : desfazer();
|
|
535
|
+
}
|
|
529
536
|
let contaParaReconciliar = null;
|
|
530
537
|
const observado = comLockWatcher(pasta, () => {
|
|
531
538
|
const eventos = (0, ledger_1.lerLedger)(dir), resultadoId = `codex:${chave}`;
|
|
@@ -798,6 +805,10 @@ function validarRegistroWatcher(raiz, sessionId, fonte) {
|
|
|
798
805
|
throw new Error('fonte nativa claude-agents em sessão de outro runtime');
|
|
799
806
|
if (registro.controlador && sessao.runtime !== 'codex')
|
|
800
807
|
throw new Error('fonte do controller Codex em sessão de outro runtime');
|
|
808
|
+
if (registro.grok !== undefined && sessao.runtime !== 'grok')
|
|
809
|
+
throw new Error('fonte do grok em sessão de outro runtime');
|
|
810
|
+
if (sessao.runtime === 'grok' && registro.grok === undefined)
|
|
811
|
+
throw new Error('sessão grok sem a fonte do grok');
|
|
801
812
|
return { thread, sessao, dir, eventos, registro };
|
|
802
813
|
}
|
|
803
814
|
/** Validação profunda feita pelo laço: prontidão eventual nunca impede o registro do vínculo. */
|
|
@@ -807,6 +818,10 @@ function validarFonteWatcher(raiz, sessionId, fonte) {
|
|
|
807
818
|
(0, session_watcher_claude_1.validarFonteClaude)(eventos, sessionId, registro);
|
|
808
819
|
return;
|
|
809
820
|
}
|
|
821
|
+
if (registro.grok !== undefined) {
|
|
822
|
+
(0, session_watcher_grok_1.validarFonteGrok)(eventos, sessionId, registro, dir);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
810
825
|
if (registro.controlador) {
|
|
811
826
|
const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessionId).at(-1);
|
|
812
827
|
if (despacho?.controlador !== undefined && despacho.controlador !== registro.controlador)
|
package/dist/sessoes-contas.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RUNTIMES_DAS_SESSOES = void 0;
|
|
36
37
|
exports.pidVivo = pidVivo;
|
|
37
38
|
exports.ehFantasma = ehFantasma;
|
|
38
39
|
exports.encerradaNaLista = encerradaNaLista;
|
|
@@ -49,6 +50,10 @@ exports.sessoesVivasPorPerfil = sessoesVivasPorPerfil;
|
|
|
49
50
|
* perfil do store, sem repetir diretorio, e cada sessao volta com o id do perfil dela (nunca o
|
|
50
51
|
* diretorio nem nada de dentro dele).
|
|
51
52
|
*
|
|
53
|
+
* RM-067 (F4): o `grok` entra com a pasta `sessions/` que o proprio runtime grava em cada casa (`GROK_HOME`), lida so
|
|
54
|
+
* pelos nomes e pelo mtime do `updates.jsonl`. A conta do processo dele so entra quando essa pasta existe: maquina sem o
|
|
55
|
+
* `grok` nao ganha fonte nova.
|
|
56
|
+
*
|
|
52
57
|
* Fantasma (D5): sessao claude-bg com estado nao terminal sem pid, ou com pid sem processo. O
|
|
53
58
|
* `claude agents` guarda o registro, mas nao ha quem trabalhe: ela nao ocupa vaga, e
|
|
54
59
|
* `ork sessions limpar-fantasmas` solta o vinculo do `ork` sem tocar no runtime.
|
|
@@ -57,6 +62,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
57
62
|
const path = __importStar(require("node:path"));
|
|
58
63
|
const claude_bg_1 = require("./adapters/claude-bg");
|
|
59
64
|
const codex_1 = require("./adapters/codex");
|
|
65
|
+
const grok_1 = require("./adapters/grok");
|
|
60
66
|
const runtime_profiles_1 = require("./runtime-profiles");
|
|
61
67
|
const TERMINAIS = ['done', 'completed', 'exited', 'stopped', 'failed'];
|
|
62
68
|
const estadoDe = (s) => String(s.state ?? s.status ?? '').trim().toLowerCase();
|
|
@@ -108,17 +114,26 @@ function contasDeSessoes(raiz, env = process.env) {
|
|
|
108
114
|
try {
|
|
109
115
|
for (const p of (0, runtime_profiles_1.lerPerfis)(raiz).perfis) {
|
|
110
116
|
const d = (0, runtime_profiles_1.perfilDeDespacho)(p);
|
|
111
|
-
|
|
117
|
+
if (exports.RUNTIMES_DAS_SESSOES.includes(d.runtime))
|
|
118
|
+
incluir(d.runtime, d, (0, runtime_profiles_1.diretorioDoPerfil)(d));
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
catch (e) {
|
|
115
122
|
fontes.push({ origem: 'perfis de conta (ork accounts)', ok: false, detalhe: e.message });
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
|
-
for (const runtime of runtime_profiles_1.
|
|
125
|
+
for (const runtime of runtime_profiles_1.RUNTIMES_COM_INVENTARIO)
|
|
119
126
|
incluir(runtime, null, (0, runtime_profiles_1.diretorioImplicito)(runtime, env));
|
|
127
|
+
const casaGrok = (0, runtime_profiles_1.diretorioImplicito)('grok', env);
|
|
128
|
+
if (fs.existsSync(path.join(casaGrok, 'sessions')))
|
|
129
|
+
incluir('grok', null, casaGrok);
|
|
120
130
|
return { contas, fontes };
|
|
121
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* RM-067 (F4): os runtimes cujas sessoes o `ork sessions` lista por conta. O grok entra aqui, e nao em
|
|
134
|
+
* `RUNTIMES_COM_INVENTARIO`, que tambem diz os runtimes de assinatura do #Noctum (a medicao dele segue sem o grok).
|
|
135
|
+
*/
|
|
136
|
+
exports.RUNTIMES_DAS_SESSOES = [...runtime_profiles_1.RUNTIMES_COM_INVENTARIO, 'grok'];
|
|
122
137
|
const rotulo = (c) => c.perfil ? `perfil ${c.perfil.id}` : 'conta do processo';
|
|
123
138
|
/**
|
|
124
139
|
* D4: as sessoes de todas as contas. Consulta que falha vira fonte `ok: false`, nunca "zero
|
|
@@ -154,7 +169,7 @@ function consultarContas(raiz, opcoes = {}) {
|
|
|
154
169
|
sessoes.push({ ...s, runtime: 'claude-bg', perfil, fantasma, viva: !fantasma && !TERMINAIS.includes(estadoDe(s)) });
|
|
155
170
|
}
|
|
156
171
|
}
|
|
157
|
-
else {
|
|
172
|
+
else if (conta.runtime === 'codex') {
|
|
158
173
|
const r = (0, codex_1.consultarRollouts)(opcoes.todas, conta.dir);
|
|
159
174
|
// A conta do processo segue com o caminho de antes; a do perfil vai pelo id, nunca pela pasta.
|
|
160
175
|
fontes.push(...(conta.perfil ? r.resultadosFontes.map(f => ({ ...f,
|
|
@@ -164,6 +179,17 @@ function consultarContas(raiz, opcoes = {}) {
|
|
|
164
179
|
sessoes.push({ ...s, runtime: 'codex', perfil, fantasma: false, viva: estadoDe(s) !== 'completed' && recente });
|
|
165
180
|
}
|
|
166
181
|
}
|
|
182
|
+
else if (conta.runtime === 'grok') {
|
|
183
|
+
// RM-067 (F4): a pasta da sessao que o runtime grava; viva e a escrita recente no updates.jsonl, como no codex.
|
|
184
|
+
const r = (0, grok_1.consultarSessoesGrok)(conta.dir);
|
|
185
|
+
const origem = `grok sessions (${rotulo(conta)})`;
|
|
186
|
+
fontes.push(r.ausente ? { origem, ok: true, ausente: true, detalhe: 'nenhuma sessao grok nesta conta ainda' }
|
|
187
|
+
: { origem, ok: r.ok, detalhe: r.detalhe });
|
|
188
|
+
for (const s of r.sessoes) {
|
|
189
|
+
const recente = typeof s.atividadeEm === 'number' && agoraMs - s.atividadeEm <= janelaMs;
|
|
190
|
+
sessoes.push({ ...s, runtime: 'grok', perfil, fantasma: false, viva: recente });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
167
193
|
}
|
|
168
194
|
return { sessoes, fontes, contas: base.contas };
|
|
169
195
|
}
|
|
@@ -176,10 +202,12 @@ function consultarContas(raiz, opcoes = {}) {
|
|
|
176
202
|
function estadosNasContas(raiz, opcoes = {}) {
|
|
177
203
|
const r = consultarContas(raiz, opcoes);
|
|
178
204
|
const claude = r.fontes.filter(f => f.origem.startsWith('claude agents'));
|
|
179
|
-
if (claude.length > 0 && !claude.some(f => f.ok) && !r.sessoes.some(s => s.runtime === 'codex'))
|
|
205
|
+
if (claude.length > 0 && !claude.some(f => f.ok) && !r.sessoes.some(s => s.runtime === 'codex' || s.runtime === 'grok'))
|
|
180
206
|
return null;
|
|
207
|
+
// RM-067 (F4): a sessao grok sem escrita recente fica `idle`; o runtime nao grava estado terminal que o ork leia sem o conteudo.
|
|
181
208
|
return new Map(r.sessoes.map(s => [s.sessionId,
|
|
182
|
-
s.fantasma ? 'fantasma' : s.runtime === 'codex' ? (s.viva ? 'working' : estadoDe(s))
|
|
209
|
+
s.fantasma ? 'fantasma' : s.runtime === 'codex' ? (s.viva ? 'working' : estadoDe(s))
|
|
210
|
+
: s.runtime === 'grok' ? (s.viva ? 'working' : 'idle') : estadoDe(s)]));
|
|
183
211
|
}
|
|
184
212
|
/**
|
|
185
213
|
* RM-056 (D3): sessoes vivas de cada perfil do runtime nesta maquina, a carga do rodizio por
|