@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.
Files changed (118) hide show
  1. package/adapters/claude-code/agents/ork-check.md +1 -1
  2. package/adapters/claude-code/agents/ork-go.md +1 -1
  3. package/adapters/claude-code/agents/ork-goal.md +1 -1
  4. package/adapters/claude-code/agents/ork-master.md +1 -1
  5. package/adapters/claude-code/agents/ork-plan.md +1 -1
  6. package/adapters/claude-code/agents/ork-ship.md +1 -1
  7. package/adapters/claude-code/commands/ork.md +1 -1
  8. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
  9. package/adapters/codex/skills/ork/SKILL.md +1 -1
  10. package/adapters/openclaw/openclaw.plugin.json +1 -1
  11. package/adapters/openclaw/package.json +1 -1
  12. package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
  13. package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
  14. package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
  15. package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
  16. package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
  17. package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
  18. package/assets/adapters-en/claude-code/commands/ork.md +1 -1
  19. package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
  20. package/assets/orkmind_fixture.py +4 -1
  21. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
  22. package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
  23. package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
  24. package/dist/adapters/claude-bg.js +9 -7
  25. package/dist/adapters/grok-events.js +227 -0
  26. package/dist/adapters/grok-mcp-servidor.js +622 -0
  27. package/dist/adapters/grok-mcp.js +178 -0
  28. package/dist/adapters/grok.js +686 -0
  29. package/dist/agendador-cpu.js +92 -0
  30. package/dist/branch-de-estado.js +7 -2
  31. package/dist/changelog-check.js +217 -0
  32. package/dist/check-agil.js +289 -25
  33. package/dist/check-paralelo.js +73 -18
  34. package/dist/ci.js +3 -2
  35. package/dist/claim-lint.js +29 -0
  36. package/dist/claims.js +4 -0
  37. package/dist/conducao.js +5 -0
  38. package/dist/docs.js +16 -16
  39. package/dist/doctor-forja.js +1 -3
  40. package/dist/doctor.js +1 -1
  41. package/dist/entrega-pr.js +10 -10
  42. package/dist/fabrica-estado.js +20 -11
  43. package/dist/fila-pesada.js +121 -11
  44. package/dist/hitl-classificacao.js +1 -0
  45. package/dist/horario.js +4 -1
  46. package/dist/index.js +63 -35
  47. package/dist/integracoes-locais.js +2 -0
  48. package/dist/intelligence-graph-cli.js +27 -9
  49. package/dist/locale-accounts.js +11 -3
  50. package/dist/locale-ajuda.js +10 -6
  51. package/dist/locale-claims.js +16 -2
  52. package/dist/locale-forja.js +2 -4
  53. package/dist/locale-grafo.js +56 -1
  54. package/dist/locale-maestro.js +38 -0
  55. package/dist/locale-network.js +8 -6
  56. package/dist/locale-onboarding.js +14 -0
  57. package/dist/locale-operacao-restante.js +2 -2
  58. package/dist/locale-preflight.js +80 -0
  59. package/dist/locale-projetos.js +6 -0
  60. package/dist/locale-prompts.js +8 -8
  61. package/dist/locale-pulse.js +4 -0
  62. package/dist/locale-retry.js +56 -2
  63. package/dist/locale-security-scan.js +35 -1
  64. package/dist/locale-ship.js +10 -2
  65. package/dist/locale-verify.js +15 -0
  66. package/dist/locale.js +24 -7
  67. package/dist/maestro-cli.js +12 -17
  68. package/dist/manifest.js +6 -1
  69. package/dist/mcp-install.js +24 -13
  70. package/dist/mcp-server.js +62 -33
  71. package/dist/mcp-verify-async.js +353 -0
  72. package/dist/mcp-verify-worker.js +17 -0
  73. package/dist/mcp-verify.js +10 -1
  74. package/dist/merge-da-entrega.js +85 -0
  75. package/dist/network-roadmap.js +18 -14
  76. package/dist/noctum-aceite.js +154 -14
  77. package/dist/noctum-cli.js +69 -12
  78. package/dist/noctum-entrega.js +55 -7
  79. package/dist/noctum-envelope.js +1 -1
  80. package/dist/noctum-execucao.js +5 -1
  81. package/dist/noctum-fechamento.js +18 -7
  82. package/dist/noctum-fila.js +6 -1
  83. package/dist/noctum-guarda.js +81 -4
  84. package/dist/noctum-maquinas.js +5 -1
  85. package/dist/noctum-modo.js +35 -7
  86. package/dist/noctum-reconciliacao.js +4 -1
  87. package/dist/noctum-rede.js +102 -0
  88. package/dist/noctum-relatorio.js +44 -10
  89. package/dist/noctum-retomada-rede.js +71 -0
  90. package/dist/noctum-vigia.js +49 -9
  91. package/dist/parado-no-condutor.js +14 -15
  92. package/dist/pedido-conducao.js +51 -0
  93. package/dist/phase.js +43 -15
  94. package/dist/postura-local.js +7 -3
  95. package/dist/preflight.js +27 -27
  96. package/dist/prompts.js +4 -2
  97. package/dist/pulse.js +4 -2
  98. package/dist/ratelimit.js +1 -0
  99. package/dist/rede.js +24 -12
  100. package/dist/retry.js +115 -10
  101. package/dist/runtime-context.js +165 -45
  102. package/dist/runtime-escada.js +7 -1
  103. package/dist/runtime-profiles.js +27 -17
  104. package/dist/runtimes.js +42 -5
  105. package/dist/security-scan/agil.js +18 -7
  106. package/dist/security-scan/cli.js +2 -2
  107. package/dist/security-scan/executor.js +8 -0
  108. package/dist/security-scan/report.js +1 -1
  109. package/dist/session-watcher-grok.js +425 -0
  110. package/dist/session-watcher.js +52 -11
  111. package/dist/sessoes-contas.js +33 -5
  112. package/dist/setup.js +11 -11
  113. package/dist/varredura-acoes.js +8 -15
  114. package/dist/verify.js +191 -43
  115. package/package.json +2 -2
  116. package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
  117. package/skills/phases/check-quality/SKILL.md +1 -1
  118. 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
+ }
@@ -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: 'conducao.batida: sessão verificada sem watcher e sem evento útil', fonteVencedora: null,
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
- (0, ledger_1.registrar)(dir, threadId, 'sessao_morta', dados);
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: 'sessão verificada sem watcher e sem evento útil além do limite' };
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)