@orkastery/cli 0.6.0 → 0.6.2
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/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +1 -1
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/ork.md +1 -1
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
- package/adapters/codex/skills/ork/SKILL.md +1 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
- package/assets/adapters-en/claude-code/commands/ork.md +1 -1
- package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
- package/assets/orkmind_fixture.py +4 -1
- package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
- package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
- package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
- package/dist/adapters/claude-bg.js +9 -7
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +622 -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/changelog-check.js +217 -0
- package/dist/check-agil.js +289 -25
- package/dist/check-paralelo.js +73 -18
- package/dist/ci.js +3 -2
- package/dist/claim-lint.js +29 -0
- package/dist/claims.js +4 -0
- package/dist/conducao.js +5 -0
- package/dist/docs.js +16 -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/fila-pesada.js +121 -11
- package/dist/hitl-classificacao.js +1 -0
- package/dist/horario.js +4 -1
- package/dist/index.js +63 -35
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +27 -9
- package/dist/locale-accounts.js +11 -3
- package/dist/locale-ajuda.js +10 -6
- package/dist/locale-claims.js +16 -2
- package/dist/locale-forja.js +2 -4
- package/dist/locale-grafo.js +56 -1
- package/dist/locale-maestro.js +38 -0
- package/dist/locale-network.js +8 -6
- package/dist/locale-onboarding.js +14 -0
- package/dist/locale-operacao-restante.js +2 -2
- package/dist/locale-preflight.js +80 -0
- package/dist/locale-projetos.js +6 -0
- package/dist/locale-prompts.js +8 -8
- package/dist/locale-pulse.js +4 -0
- package/dist/locale-retry.js +56 -2
- package/dist/locale-security-scan.js +35 -1
- package/dist/locale-ship.js +10 -2
- package/dist/locale-verify.js +15 -0
- package/dist/locale.js +24 -7
- package/dist/maestro-cli.js +12 -17
- package/dist/manifest.js +6 -1
- package/dist/mcp-install.js +24 -13
- package/dist/mcp-server.js +62 -33
- package/dist/mcp-verify-async.js +353 -0
- package/dist/mcp-verify-worker.js +17 -0
- package/dist/mcp-verify.js +10 -1
- 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/pedido-conducao.js +51 -0
- package/dist/phase.js +43 -15
- package/dist/postura-local.js +7 -3
- package/dist/preflight.js +27 -27
- package/dist/prompts.js +4 -2
- package/dist/pulse.js +4 -2
- package/dist/ratelimit.js +1 -0
- package/dist/rede.js +24 -12
- package/dist/retry.js +115 -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/cli.js +2 -2
- package/dist/security-scan/executor.js +8 -0
- package/dist/security-scan/report.js +1 -1
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +52 -11
- package/dist/sessoes-contas.js +33 -5
- package/dist/setup.js +11 -11
- package/dist/varredura-acoes.js +8 -15
- package/dist/verify.js +191 -43
- package/package.json +2 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +1 -1
- package/skills/phases/go-implementation/SKILL.md +1 -1
|
@@ -143,7 +143,7 @@ function renderMarkdown(report) {
|
|
|
143
143
|
else {
|
|
144
144
|
out.push(`| ${m.tableTool} | ${m.tableExecutor} | ${m.tableState} | ${m.tableReason} |`, '| --- | --- | --- | --- |');
|
|
145
145
|
for (const r of report.tools) {
|
|
146
|
-
out.push(`| ${cell(r.name)} | ${r.executor ?? m.noExecutor} | ${m.states[r.state] ?? r.state} | ${cell(r.reason ?? '')} |`);
|
|
146
|
+
out.push(`| ${cell(r.name)} | ${r.executor ?? m.noExecutor} | ${m.states[r.state] ?? r.state} | ${cell(m.reason(r.reason ?? ''))} |`);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
const news = report.findings.filter(f => f.status === 'new');
|
|
@@ -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,8 +58,20 @@ 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");
|
|
64
|
+
const pedido_conducao_1 = require("./pedido-conducao");
|
|
65
|
+
/** O gate estreito da própria sessão vence diagnósticos genéricos do watcher. */
|
|
66
|
+
function pedidoDaConducao(eventos, sessionId, despachoEm) {
|
|
67
|
+
const evento = eventos.filter(e => e.tipo === 'gate_blocked' && e.origem === 'mcp.request_conduction' &&
|
|
68
|
+
e.sessionId === sessionId && e.despachoEm === despachoEm && typeof e.motivo === 'string' &&
|
|
69
|
+
pedido_conducao_1.MOTIVOS_DE_PEDIDO_A_CONDUCAO.includes(e.motivo) &&
|
|
70
|
+
typeof e.comando === 'string' && typeof e.despacho === 'string').at(-1);
|
|
71
|
+
return evento ? { motivo: evento.motivo, comando: evento.comando,
|
|
72
|
+
detalhe: typeof evento.detalhe === 'string' ? evento.detalhe : 'sessão solicitou ação da condução',
|
|
73
|
+
despacho: evento.despacho } : null;
|
|
74
|
+
}
|
|
63
75
|
/** Falhas operacionais e bloqueios sem retry automático não alegam conclusão do produto. */
|
|
64
76
|
function bloqueioPreservado(terminal) {
|
|
65
77
|
return terminal.classificacao === 'gate_blocked' && terminal.motivo !== null &&
|
|
@@ -286,7 +298,7 @@ function conferirRegistro(dir, registro) {
|
|
|
286
298
|
* com o dono da remoção.
|
|
287
299
|
*/
|
|
288
300
|
function desfazerNaThreadRemovida(pasta, criadas) {
|
|
289
|
-
for (const cursor of ['cursor.json', 'cursor-claude.json']) {
|
|
301
|
+
for (const cursor of ['cursor.json', 'cursor-claude.json', 'cursor-grok.json']) {
|
|
290
302
|
try {
|
|
291
303
|
fs.rmSync(path.join(pasta, cursor), { force: true });
|
|
292
304
|
}
|
|
@@ -393,7 +405,7 @@ function reconciliarSessaoSemWatcher(carregado, threadId, agoraMs = Date.now())
|
|
|
393
405
|
const ignorada = (detalhe) => ({ thread: threadId,
|
|
394
406
|
sessionId: sessao?.sessionId ?? null, estado: 'ignorada', detalhe });
|
|
395
407
|
if (thread.status === 'fechada' || !sessao || sessao.verificada !== true ||
|
|
396
|
-
!['codex', 'claude-bg'].includes(sessao.runtime) || typeof sessao.despachadaEm !== 'string')
|
|
408
|
+
!['codex', 'claude-bg', 'grok'].includes(sessao.runtime) || typeof sessao.despachadaEm !== 'string')
|
|
397
409
|
return ignorada('sem sessão despachada e verificada que o watcher possa reconciliar');
|
|
398
410
|
const dir = (0, thread_1.dirThread)(carregado.raiz, threadId);
|
|
399
411
|
let eventos = (0, ledger_1.lerLedger)(dir);
|
|
@@ -473,24 +485,31 @@ function reconciliarSessaoSemWatcher(carregado, threadId, agoraMs = Date.now())
|
|
|
473
485
|
if (atuais.some(e => e.tipo === 'phase_result' && e.sessionId === sessao.sessionId &&
|
|
474
486
|
e.despachoEm === sessao.despachadaEm))
|
|
475
487
|
return;
|
|
488
|
+
const pedido = pedidoDaConducao(atuais, sessao.sessionId, sessao.despachadaEm);
|
|
476
489
|
const dados = { fase: sessao.fase, sessionId: sessao.sessionId, despachoEm: sessao.despachadaEm,
|
|
477
|
-
sensorResultId, classificacao: 'gate_blocked', motivo: 'runtime.unavailable', runtime: sessao.runtime,
|
|
478
|
-
fonte: '
|
|
490
|
+
sensorResultId, classificacao: 'gate_blocked', motivo: pedido?.motivo ?? 'runtime.unavailable', runtime: sessao.runtime,
|
|
491
|
+
fonte: pedido ? 'mcp.request_conduction: parada intencional tipada pela sessão'
|
|
492
|
+
: 'conducao.batida: sessão verificada sem watcher e sem evento útil', fonteVencedora: null,
|
|
479
493
|
ok: false, conclusaoNativa: false, conclusaoNativaAusente: 'watcher ausente e fonte indisponível além do limite',
|
|
480
494
|
estado: 'bloqueada', observadoEm: new Date(agoraMs).toISOString(), gate: 'phase.dispatch',
|
|
481
|
-
origem: 'conducao.batida', morteLatenciaMs: agoraMs - ultimoUtil
|
|
482
|
-
|
|
495
|
+
origem: 'conducao.batida', morteLatenciaMs: agoraMs - ultimoUtil,
|
|
496
|
+
...(pedido ? { detalhe: pedido.detalhe, correcao: pedido.comando, comando: pedido.comando,
|
|
497
|
+
pedidoConducao: { contrato: 'ork.pedido-conducao/v1', despacho: pedido.despacho, estado: 'aberto' } } : {}) };
|
|
498
|
+
if (!pedido)
|
|
499
|
+
(0, ledger_1.registrar)(dir, threadId, 'sessao_morta', dados);
|
|
483
500
|
(0, ledger_1.registrar)(dir, threadId, 'gate_blocked', dados);
|
|
484
501
|
(0, ledger_1.registrar)(dir, threadId, 'phase_result', dados);
|
|
485
502
|
});
|
|
486
503
|
return { thread: threadId, sessionId: sessao.sessionId, estado: 'parada',
|
|
487
|
-
detalhe:
|
|
504
|
+
detalhe: pedidoDaConducao((0, ledger_1.lerLedger)(dir), sessao.sessionId, sessao.despachadaEm)
|
|
505
|
+
? 'sessão parou com pedido tipado para a condução'
|
|
506
|
+
: 'sessão verificada sem watcher e sem evento útil além do limite' };
|
|
488
507
|
}
|
|
489
508
|
}
|
|
490
509
|
function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registrada }, opcoes) {
|
|
491
510
|
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');
|
|
511
|
+
if (sessao.runtime !== 'codex' && sessao.runtime !== 'claude-bg' && sessao.runtime !== 'grok')
|
|
512
|
+
throw new Error('watch requer sessão Codex ou claude-bg ou grok registrada');
|
|
494
513
|
const now = opcoes.agoraMs ?? Date.now();
|
|
495
514
|
if (!Number.isFinite(now))
|
|
496
515
|
throw new Error('relógio inválido');
|
|
@@ -526,6 +545,12 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
526
545
|
thread, sessao, dir, pasta, chave, agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, consulta: opcoes.consultaClaude, carregado })) ?? { ...base, ocupado: true };
|
|
527
546
|
return (0, ledger_1.threadPresente)(dir) ? r : desfazer();
|
|
528
547
|
}
|
|
548
|
+
// RM-067 (F2): o grok tem observador próprio (stream, recibo do supervisor e pasta da sessão), no mesmo lock.
|
|
549
|
+
if (sessao.runtime === 'grok') {
|
|
550
|
+
const r = comLockWatcher(pasta, () => (0, session_watcher_grok_1.observarGrokSobLock)({ raiz: carregado.raiz, thread, sessao, dir, pasta, chave,
|
|
551
|
+
agoraMs: now, limiteMs: exports.LIMITE_MORTE_MS, relogioReal: opcoes.agoraMs === undefined })) ?? { ...base, ocupado: true };
|
|
552
|
+
return (0, ledger_1.threadPresente)(dir) ? r : desfazer();
|
|
553
|
+
}
|
|
529
554
|
let contaParaReconciliar = null;
|
|
530
555
|
const observado = comLockWatcher(pasta, () => {
|
|
531
556
|
const eventos = (0, ledger_1.lerLedger)(dir), resultadoId = `codex:${chave}`;
|
|
@@ -707,6 +732,7 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
707
732
|
}
|
|
708
733
|
if (!podeTerminar)
|
|
709
734
|
return { ...base, bytesLidos };
|
|
735
|
+
const pedido = pedidoDaConducao(atuais, sessionId, sessao.despachadaEm);
|
|
710
736
|
const semTerminal = !terminal;
|
|
711
737
|
// Fato registrado para a autoridade de revisão: `null` só com terminal nativo correlacionado.
|
|
712
738
|
const conclusaoNativa = incompleto || invalido || aberto || descorrelacionado
|
|
@@ -719,6 +745,11 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
719
745
|
terminal = { classificacao: 'gate_blocked', motivo: 'runtime.silencio',
|
|
720
746
|
timestamp: new Date(now).toISOString(), turnId: null, fonte: 'processo ausente e arquivos estáveis',
|
|
721
747
|
tokens: new codex_events_1.ParserCodex().estado.tokens, duracaoMs: null };
|
|
748
|
+
if (pedido && (terminal.classificacao === 'fase_concluida' ||
|
|
749
|
+
terminal.classificacao === 'gate_blocked' && ['runtime.unavailable', 'runtime.silencio'].includes(terminal.motivo ?? ''))) {
|
|
750
|
+
terminal = { ...terminal, classificacao: 'gate_blocked', motivo: pedido.motivo,
|
|
751
|
+
fonte: `mcp.request_conduction: ${pedido.detalhe}` };
|
|
752
|
+
}
|
|
722
753
|
if (fechamento && (fechamento.exitCode !== 0 || fechamento.signal || fechamento.erro) &&
|
|
723
754
|
terminal.classificacao !== 'human.pending' && !bloqueioPreservado(terminal)) {
|
|
724
755
|
terminal = { ...terminal, classificacao: 'gate_blocked', motivo: 'runtime.unavailable' };
|
|
@@ -732,7 +763,7 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
732
763
|
? (0, trabalho_thread_1.gravarRespostaFinalDaSessao)(carregado.raiz, thread.id, sessionId, terminal.respostaFinal) : null;
|
|
733
764
|
const dados = { fase: sessao.fase, sessionId, despachoEm: sessao.despachadaEm, sensorResultId: resultadoId,
|
|
734
765
|
classificacao: terminal.classificacao, motivo: terminal.motivo, runtime: 'codex', fonte: terminal.fonte,
|
|
735
|
-
fonteVencedora: incompleto || invalido || aberto || descorrelacionado ? null : terminais[0]?.nome ?? null,
|
|
766
|
+
fonteVencedora: pedido || incompleto || invalido || aberto || descorrelacionado ? null : terminais[0]?.nome ?? null,
|
|
736
767
|
turnId: terminal.turnId, numeroTurno: terminal.numeroTurno ?? null,
|
|
737
768
|
tokens: terminal.tokens, exitCode: fechamento?.exitCode ?? null, exitCodeFonte: fechamento?.fonte ?? 'unavailable',
|
|
738
769
|
signal: fechamento?.signal ?? null, duracaoMs: fechamento?.duracaoMs ?? terminal.duracaoMs,
|
|
@@ -743,11 +774,13 @@ function observarSessaoResolvida(carregado, sessionId, { thread, sessao: registr
|
|
|
743
774
|
estado: terminal.classificacao === 'fase_concluida' ? 'concluida' : 'bloqueada',
|
|
744
775
|
observadoEm: terminal.timestamp, gate: 'phase.dispatch', origem: 'sessions.watch',
|
|
745
776
|
morteLatenciaMs: morte ? now - cursor.progressoEm : null, ...(perfil ? { perfil } : {}),
|
|
777
|
+
...(pedido ? { detalhe: pedido.detalhe, correcao: pedido.comando, comando: pedido.comando,
|
|
778
|
+
pedidoConducao: { contrato: 'ork.pedido-conducao/v1', despacho: pedido.despacho, estado: 'aberto' } } : {}),
|
|
746
779
|
...(respostaFinal ? { respostaFinal: { arquivo: (0, trabalho_thread_1.caminhoDoTrabalho)(thread.id) + respostaFinal.arquivo,
|
|
747
780
|
sha256: respostaFinal.sha256, bytes: respostaFinal.bytes, truncada: respostaFinal.truncada } } : {}),
|
|
748
781
|
...(conta ? { falhaDeConta: { motivo: conta.falha.motivo, resetEm: conta.falha.resetEm, fonte: conta.falha.fonte,
|
|
749
782
|
trecho: conta.falha.trecho } } : {}) };
|
|
750
|
-
const tipo = semTerminal && morte && !incompleto && !conta ? 'sessao_morta' : terminal.classificacao;
|
|
783
|
+
const tipo = semTerminal && morte && !incompleto && !conta && !pedido ? 'sessao_morta' : terminal.classificacao;
|
|
751
784
|
// I-33 (D12): o perfil sai do rodizio na primeira gravacao do resultado, onde a evidencia esta.
|
|
752
785
|
if (conta && perfil && !atuais.some(e => e.tipo === 'phase_result' && e.sensorResultId === resultadoId)) {
|
|
753
786
|
try {
|
|
@@ -798,6 +831,10 @@ function validarRegistroWatcher(raiz, sessionId, fonte) {
|
|
|
798
831
|
throw new Error('fonte nativa claude-agents em sessão de outro runtime');
|
|
799
832
|
if (registro.controlador && sessao.runtime !== 'codex')
|
|
800
833
|
throw new Error('fonte do controller Codex em sessão de outro runtime');
|
|
834
|
+
if (registro.grok !== undefined && sessao.runtime !== 'grok')
|
|
835
|
+
throw new Error('fonte do grok em sessão de outro runtime');
|
|
836
|
+
if (sessao.runtime === 'grok' && registro.grok === undefined)
|
|
837
|
+
throw new Error('sessão grok sem a fonte do grok');
|
|
801
838
|
return { thread, sessao, dir, eventos, registro };
|
|
802
839
|
}
|
|
803
840
|
/** Validação profunda feita pelo laço: prontidão eventual nunca impede o registro do vínculo. */
|
|
@@ -807,6 +844,10 @@ function validarFonteWatcher(raiz, sessionId, fonte) {
|
|
|
807
844
|
(0, session_watcher_claude_1.validarFonteClaude)(eventos, sessionId, registro);
|
|
808
845
|
return;
|
|
809
846
|
}
|
|
847
|
+
if (registro.grok !== undefined) {
|
|
848
|
+
(0, session_watcher_grok_1.validarFonteGrok)(eventos, sessionId, registro, dir);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
810
851
|
if (registro.controlador) {
|
|
811
852
|
const despacho = eventos.filter(e => e.tipo === 'phase_dispatch' && e.sessionId === sessionId).at(-1);
|
|
812
853
|
if (despacho?.controlador !== undefined && despacho.controlador !== registro.controlador)
|