@orkastery/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +86 -65
  3. package/adapters/README.md +30 -15
  4. package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
  5. package/adapters/claude-code/README.md +110 -8
  6. package/adapters/claude-code/commands/onboarding.md +21 -0
  7. package/adapters/claude-code/commands/ork.md +99 -27
  8. package/adapters/claude-code/hooks/hooks.json +6 -1
  9. package/adapters/claude-code/hooks/ork-sensor.js +66 -0
  10. package/adapters/codex/skills/ork/SKILL.md +57 -0
  11. package/adapters/hermes/README.md +163 -0
  12. package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
  13. package/adapters/hermes/bin/ork-brain.sh +4 -0
  14. package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
  15. package/adapters/hermes/bin/ork-maestro.sh +6 -0
  16. package/adapters/hermes/bin/ork-master-enviar.py +84 -0
  17. package/adapters/hermes/bin/ork-objective-message.sh +17 -0
  18. package/adapters/hermes/bin/ork-objective-status.sh +12 -0
  19. package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
  20. package/adapters/hermes/hermes.plugin.json +26 -4
  21. package/adapters/hermes/hitl-ingress/__init__.py +356 -0
  22. package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
  24. package/adapters/openclaw/README.md +135 -26
  25. package/adapters/openclaw/bin/ork-brain.sh +4 -0
  26. package/adapters/openclaw/construir.sh +19 -0
  27. package/adapters/openclaw/dist/hitl-ingress.js +199 -0
  28. package/adapters/openclaw/dist/index.js +437 -0
  29. package/adapters/openclaw/openclaw.plugin.json +39 -134
  30. package/adapters/openclaw/package.json +26 -0
  31. package/adapters/openclaw/src/hitl-ingress.ts +180 -0
  32. package/adapters/openclaw/src/index.ts +458 -0
  33. package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
  34. package/adapters/openclaw/tsconfig.json +15 -0
  35. package/assets/docs/markdownlint-cli2.jsonc +22 -0
  36. package/assets/docs/padroes/documentacao-de-produto.md +133 -0
  37. package/assets/docs/padroes/roadmap-de-produto.md +98 -0
  38. package/assets/docs/produto/README.md +14 -0
  39. package/assets/docs/produto/_modelo-feature.md +59 -0
  40. package/assets/docs/roadmap/README.md +14 -0
  41. package/assets/docs/roadmap/_modelo-item.md +73 -0
  42. package/assets/orkmind-native-schema.json +32 -0
  43. package/assets/orkmind_bridge.py +357 -0
  44. package/assets/orkmind_fixture.py +67 -0
  45. package/assets/orkmind_prospective.py +138 -0
  46. package/assets/reference-tariffs-i07.json +23 -0
  47. package/dist/adapters/claude-bg.js +488 -39
  48. package/dist/adapters/codex-controller-sensor.js +449 -0
  49. package/dist/adapters/codex-controller-worker.js +361 -0
  50. package/dist/adapters/codex-controller.js +252 -0
  51. package/dist/adapters/codex-events.js +205 -0
  52. package/dist/adapters/codex-question.js +26 -0
  53. package/dist/adapters/codex-runner.js +133 -0
  54. package/dist/adapters/codex.js +394 -0
  55. package/dist/agents-md.js +84 -0
  56. package/dist/auditoria.js +11 -8
  57. package/dist/auditrun.js +5 -4
  58. package/dist/board.js +173 -22
  59. package/dist/branch-de-estado.js +136 -0
  60. package/dist/canarios-hitl.js +177 -0
  61. package/dist/canarios-i43.js +543 -0
  62. package/dist/canarios-pulse.js +147 -0
  63. package/dist/canarios-sensores.js +129 -0
  64. package/dist/canarios.js +111 -2
  65. package/dist/catalogo.js +9 -0
  66. package/dist/ci.js +216 -0
  67. package/dist/ciclos.js +2 -1
  68. package/dist/claim-lint.js +64 -0
  69. package/dist/claims.js +60 -0
  70. package/dist/company-brain-capture.js +195 -0
  71. package/dist/company-brain-cli.js +123 -0
  72. package/dist/company-brain-client.js +61 -0
  73. package/dist/company-brain-contract.js +166 -0
  74. package/dist/company-brain-journal.js +192 -0
  75. package/dist/company-brain-mcp.js +33 -0
  76. package/dist/company-brain-migration.js +60 -0
  77. package/dist/company-brain-source.js +177 -0
  78. package/dist/company-brain-worker.js +18 -0
  79. package/dist/conducao-texto.js +61 -0
  80. package/dist/conducao.js +876 -0
  81. package/dist/contrato-publico.js +39 -0
  82. package/dist/creation-operation-store.js +251 -0
  83. package/dist/creation-operation.js +148 -0
  84. package/dist/decisao-autonoma.js +183 -0
  85. package/dist/delegation.js +79 -0
  86. package/dist/demo.js +120 -0
  87. package/dist/docs.js +828 -0
  88. package/dist/doctor.js +183 -24
  89. package/dist/entrega-pr.js +117 -0
  90. package/dist/escopo-escrita.js +71 -0
  91. package/dist/estado-thread.js +222 -0
  92. package/dist/evalrunner.js +12 -0
  93. package/dist/fabrica-estado.js +329 -0
  94. package/dist/fabrica-publicar.js +76 -0
  95. package/dist/fix.js +21 -0
  96. package/dist/gates.js +55 -9
  97. package/dist/handoff.js +39 -23
  98. package/dist/hitl-canais.js +333 -0
  99. package/dist/hitl-classificacao.js +131 -0
  100. package/dist/hitl-contract.js +465 -0
  101. package/dist/hitl-estado.js +136 -0
  102. package/dist/hitl-gates.js +554 -0
  103. package/dist/hitl-ingress-receipt.js +382 -0
  104. package/dist/hitl-local-atestado.js +57 -0
  105. package/dist/hitl-local-receipt.js +328 -0
  106. package/dist/hitl-local.js +143 -0
  107. package/dist/hitl-lock.js +139 -0
  108. package/dist/hitl-lote.js +223 -0
  109. package/dist/hitl-native-offer.js +97 -0
  110. package/dist/hitl-native.js +65 -0
  111. package/dist/hitl-presentation.js +209 -0
  112. package/dist/hitl-public-receipt.js +176 -0
  113. package/dist/hitl-resumo.js +209 -0
  114. package/dist/hitl-sessions.js +475 -0
  115. package/dist/hitl.js +711 -0
  116. package/dist/horario.js +269 -0
  117. package/dist/hosts.js +198 -22
  118. package/dist/index.js +1736 -93
  119. package/dist/indice.js +99 -0
  120. package/dist/init.js +28 -3
  121. package/dist/integracoes-locais.js +17 -0
  122. package/dist/leases.js +65 -21
  123. package/dist/ledger-stats.js +272 -0
  124. package/dist/ledger.js +107 -2
  125. package/dist/licoes.js +215 -0
  126. package/dist/liveness.js +218 -0
  127. package/dist/maestro-actions.js +51 -0
  128. package/dist/maestro-authority.js +152 -0
  129. package/dist/maestro-cli.js +97 -0
  130. package/dist/maestro-contract.js +55 -0
  131. package/dist/maestro-discovery.js +132 -0
  132. package/dist/maestro-runtime.js +268 -0
  133. package/dist/maestro-snapshot.js +86 -0
  134. package/dist/maestro-sources.js +224 -0
  135. package/dist/manifest.js +165 -6
  136. package/dist/maquina.js +102 -0
  137. package/dist/master-audit.js +101 -0
  138. package/dist/master-batch.js +43 -0
  139. package/dist/master-digest.js +149 -0
  140. package/dist/master-migracao.js +155 -0
  141. package/dist/master.js +361 -77
  142. package/dist/mcp-artifacts.js +234 -0
  143. package/dist/mcp-git.js +433 -0
  144. package/dist/mcp-install.js +310 -0
  145. package/dist/mcp-maestro.js +25 -0
  146. package/dist/mcp-server.js +469 -0
  147. package/dist/mcp-ship.js +436 -0
  148. package/dist/mcp-verify.js +173 -0
  149. package/dist/memoria-humana.js +207 -0
  150. package/dist/memoria.js +314 -78
  151. package/dist/memory-migration.js +279 -0
  152. package/dist/memory-prospective.js +148 -0
  153. package/dist/modos-migracao.js +191 -0
  154. package/dist/modos.js +151 -15
  155. package/dist/monitor-lock.js +110 -0
  156. package/dist/objective.js +449 -0
  157. package/dist/ocupacao.js +267 -0
  158. package/dist/onboarding.js +303 -0
  159. package/dist/orkmind.js +389 -90
  160. package/dist/orquestracao.js +96 -69
  161. package/dist/phase.js +596 -150
  162. package/dist/playbook-capabilities.js +150 -0
  163. package/dist/playbook-contracts.js +188 -0
  164. package/dist/portfolio-context.js +61 -0
  165. package/dist/portfolio.js +180 -0
  166. package/dist/preflight.js +207 -0
  167. package/dist/process-audit.js +112 -0
  168. package/dist/project-state.js +93 -0
  169. package/dist/prompts.js +25 -5
  170. package/dist/prova-minima.js +112 -0
  171. package/dist/pulse-cadencia.js +163 -0
  172. package/dist/pulse-consentimento.js +260 -0
  173. package/dist/pulse-delivery.js +323 -0
  174. package/dist/pulse-resposta.js +542 -0
  175. package/dist/pulse.js +262 -0
  176. package/dist/ratelimit.js +35 -2
  177. package/dist/recall.js +80 -3
  178. package/dist/redacao-saida.js +45 -0
  179. package/dist/redacao-url.js +66 -0
  180. package/dist/retry.js +637 -115
  181. package/dist/roadmap-reservas.js +243 -0
  182. package/dist/runtime-ambiente.js +38 -0
  183. package/dist/runtime-context.js +215 -0
  184. package/dist/runtime-profiles.js +869 -0
  185. package/dist/runtimes.js +103 -0
  186. package/dist/sandbox.js +9 -0
  187. package/dist/session-events.js +203 -0
  188. package/dist/session-watcher-claude.js +637 -0
  189. package/dist/session-watcher.js +683 -0
  190. package/dist/sessoes-adopt.js +154 -0
  191. package/dist/sessoes-inventario.js +159 -0
  192. package/dist/sessoes.js +16 -35
  193. package/dist/setup.js +691 -0
  194. package/dist/ship.js +150 -15
  195. package/dist/slug.js +1 -1
  196. package/dist/thread-close.js +115 -0
  197. package/dist/thread.js +142 -28
  198. package/dist/tokens.js +1 -1
  199. package/dist/util.js +4 -2
  200. package/dist/verify-sandbox.js +265 -0
  201. package/dist/verify.js +270 -25
  202. package/dist/versao.js +60 -0
  203. package/dist/worktree.js +31 -2
  204. package/dist/write-activation.js +291 -0
  205. package/dist/yaml.js +2 -0
  206. package/eval/casos/onboarding.json +102 -0
  207. package/eval/casos/orkastery-bootstrap.json +35 -1
  208. package/eval/casos/scope-check-capability-map.json +45 -7
  209. package/eval/casos/ship-release.json +2 -2
  210. package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
  211. package/eval/fixtures/b2-master-log/caso.json +1 -1
  212. package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
  213. package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
  214. package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
  215. package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
  216. package/eval/fixtures/fx-codex-dry/caso.json +18 -0
  217. package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
  218. package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
  219. package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
  220. package/eval/fixtures/fx-happy/caso.json +6 -4
  221. package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
  222. package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
  223. package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
  224. package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
  225. package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
  226. package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
  227. package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
  228. package/eval/fixtures/fx-omnicanal/caso.json +17 -0
  229. package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
  230. package/monitor/company-brain.cjs +17 -0
  231. package/monitor/pulse-scope.cjs +45 -0
  232. package/monitor/pulse.cron +18 -0
  233. package/monitor/varredura-pulse.sh +27 -0
  234. package/package.json +25 -4
  235. package/schemas/claims.schema.json +25 -0
  236. package/schemas/company-brain.schema.json +1158 -0
  237. package/schemas/creation-operation.schema.json +365 -0
  238. package/schemas/maestro-snapshot.schema.json +2716 -0
  239. package/skills/core/onboarding/SKILL.md +50 -0
  240. package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
  241. package/skills/core/thread-state/SKILL.md +3 -1
  242. package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
@@ -0,0 +1,361 @@
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
+ /** Dono exclusivo do stdin app-server. IPC local autenticado pelo uid/diretório 0700. */
37
+ const fs = __importStar(require("node:fs"));
38
+ const net = __importStar(require("node:net"));
39
+ const readline = __importStar(require("node:readline"));
40
+ const node_child_process_1 = require("node:child_process");
41
+ const node_crypto_1 = require("node:crypto");
42
+ const codex_question_1 = require("./codex-question");
43
+ const runtime_context_1 = require("../runtime-context");
44
+ const codex_controller_1 = require("./codex-controller");
45
+ function gravar(nome, valor) {
46
+ const tmp = `${nome}.${(0, node_crypto_1.randomUUID)()}.tmp`;
47
+ fs.writeFileSync(tmp, JSON.stringify(valor), { flag: 'wx', mode: 0o600 });
48
+ fs.renameSync(tmp, nome);
49
+ }
50
+ async function cliente() {
51
+ (0, codex_controller_1.conferirDiretorio)(process.cwd());
52
+ const entrada = fs.readFileSync(0, 'utf8');
53
+ const socket = net.createConnection('control.sock');
54
+ socket.setTimeout(11000, () => socket.destroy(new Error('timeout')));
55
+ socket.once('connect', () => socket.end(entrada + '\n'));
56
+ socket.on('data', b => process.stdout.write(b));
57
+ socket.on('error', () => { process.exitCode = 1; });
58
+ }
59
+ async function servidor() {
60
+ (0, codex_controller_1.conferirDiretorio)(process.cwd());
61
+ const bootstrap = JSON.parse(fs.readFileSync(0, 'utf8'));
62
+ const pedido = bootstrap.pedido;
63
+ const state = { instancia: bootstrap.instancia, processoController: (0, codex_controller_1.identidadeDoProcesso)(process.pid), vinculo: pedido.vinculo, cwd: pedido.cwd,
64
+ sessionId: '', pid: process.pid, estado: 'starting' };
65
+ const salvar = () => gravar('state.json', state);
66
+ salvar();
67
+ const child = (0, node_child_process_1.spawn)('codex', ['app-server', '--listen', 'stdio://', '-c', 'model_provider="openai"'], { cwd: pedido.cwd, env: process.env, stdio: ['pipe', 'pipe', 'pipe'] });
68
+ state.runtimePid = child.pid;
69
+ // PID vem do ChildProcess criado aqui; starttime evita confundir reutilização de PID.
70
+ if (child.pid && process.platform === 'linux') {
71
+ try {
72
+ state.processoRuntime = (0, codex_controller_1.identidadeDoProcesso)(child.pid);
73
+ state.runtimeInicio = state.processoRuntime.inicio;
74
+ }
75
+ catch { /* falha de spawn será registrada pelo evento close */ }
76
+ }
77
+ salvar();
78
+ child.stdin.on('error', () => { });
79
+ child.stderr.resume(); // nunca persistir stderr potencialmente sensível
80
+ let seq = 0, vivo = true, pergunta = null, intencao = null;
81
+ const chamadas = new Map();
82
+ const send = (v) => { if (!vivo)
83
+ throw new Error('runtime encerrado'); child.stdin.write(JSON.stringify(v) + '\n'); };
84
+ const rpc = (method, params) => new Promise((resolve, reject) => {
85
+ const id = ++seq, timeout = setTimeout(() => { chamadas.delete(id); reject(new Error('runtime.unavailable: timeout nativo')); }, 8000);
86
+ chamadas.set(id, { resolve: v => { clearTimeout(timeout); resolve(v); }, reject: e => { clearTimeout(timeout); reject(e); } });
87
+ send({ id, method, params });
88
+ });
89
+ const aguardar = async (condicao) => {
90
+ const fim = Date.now() + 8000;
91
+ while (!condicao() && vivo && Date.now() < fim)
92
+ await new Promise(r => setTimeout(r, 20));
93
+ if (!condicao())
94
+ throw new Error('runtime.unavailable: confirmação nativa ausente');
95
+ };
96
+ let encerrando = false;
97
+ const encerrar = async (motivo) => {
98
+ if (encerrando || !vivo)
99
+ return;
100
+ encerrando = true;
101
+ state.encerramentoSolicitado = motivo;
102
+ state.estado = 'unavailable';
103
+ salvar();
104
+ try {
105
+ if (state.sessionId && state.turno && !state.terminal)
106
+ await rpc('turn/interrupt', { threadId: state.sessionId, turnId: state.turno });
107
+ }
108
+ catch { /* EOF próprio abaixo; sem confirmação nativa não inventar terminal */ }
109
+ finally {
110
+ child.stdin.end();
111
+ }
112
+ };
113
+ const inicio = performance.now();
114
+ const vigia = setInterval(() => {
115
+ let stop;
116
+ try {
117
+ stop = JSON.parse(fs.readFileSync('stop-request.json', 'utf8'));
118
+ }
119
+ catch { /* não solicitado */ }
120
+ if (stop?.instancia === state.instancia)
121
+ void encerrar('dispatch.cancelled');
122
+ else if (!state.pronto && Date.now() >= Date.parse(bootstrap.prazoInicial))
123
+ void encerrar('dispatch.timeout');
124
+ else if (performance.now() - inicio >= bootstrap.duracaoMaximaMs)
125
+ void encerrar('turn.timeout');
126
+ }, 50);
127
+ const recusarPedido = (codigo, method) => {
128
+ state.estado = 'unavailable';
129
+ state.limitacao = { motivo: 'runtime.unavailable', codigo, metodo: method };
130
+ delete state.perguntaNativa;
131
+ delete state.bloqueio;
132
+ pergunta = null;
133
+ salvar();
134
+ // Interrupção da MESMA thread/turno, sem responder/aprovar a ferramenta nativa.
135
+ void rpc('turn/interrupt', { threadId: state.sessionId, turnId: state.turno }).catch(() => {
136
+ state.erro = 'runtime.unavailable: interrupção de capacidade não suportada não confirmada';
137
+ salvar();
138
+ child.stdin.end();
139
+ });
140
+ };
141
+ readline.createInterface({ input: child.stdout }).on('line', line => {
142
+ let v;
143
+ try {
144
+ v = JSON.parse(line);
145
+ }
146
+ catch {
147
+ return;
148
+ }
149
+ if (!v.method && chamadas.has(v.id)) {
150
+ const p = chamadas.get(v.id);
151
+ chamadas.delete(v.id);
152
+ v.error ? p.reject(new Error('runtime.unavailable: RPC nativo recusado')) : p.resolve(v.result);
153
+ return;
154
+ }
155
+ const p = v.params;
156
+ if (p?.threadId !== state.sessionId || v.method === 'item/tool/requestUserInput' && p.turnId !== state.turno) {
157
+ if (v.id !== undefined && v.method)
158
+ recusarPedido('request.uncorrelated', 'request/unsupported');
159
+ return;
160
+ }
161
+ if (v.method === 'turn/started') {
162
+ state.turno = p.turn.id;
163
+ state.estado = 'working';
164
+ }
165
+ if (v.method === 'item/tool/requestUserInput' && p.turnId === state.turno) {
166
+ try {
167
+ state.perguntaNativa = (0, codex_question_1.projetarPergunta)(p);
168
+ pergunta = v;
169
+ state.estado = 'blocked';
170
+ state.bloqueio = JSON.stringify([p.turnId, v.id, p.itemId, state.perguntaNativa.sha256]);
171
+ }
172
+ catch (e) {
173
+ recusarPedido(e.message.replace('runtime.unavailable: ', ''), v.method);
174
+ }
175
+ }
176
+ if (v.id !== undefined && v.method !== 'item/tool/requestUserInput')
177
+ recusarPedido('request.unsupported', v.method);
178
+ if (v.method === 'serverRequest/resolved' && pergunta && p.requestId === pergunta.id) {
179
+ if (intencao) {
180
+ const ack = { ...intencao, estado: 'recebida' };
181
+ gravar(`receipt-${intencao.envioId}.json`, ack);
182
+ gravar(`native-${intencao.envioId}.json`, { metodo: v.method, requestId: p.requestId, threadId: p.threadId, turnId: state.turno, itemId: pergunta.params.itemId });
183
+ }
184
+ pergunta = null;
185
+ intencao = null;
186
+ state.estado = 'working';
187
+ delete state.bloqueio;
188
+ delete state.perguntaNativa;
189
+ }
190
+ if (v.method === 'turn/completed' && p.turn.id === state.turno) {
191
+ // I-33 (D12): o erro nativo do turno (so presente em `failed`) vai ao estado, limitado e sem
192
+ // controle, para o observador classificar cota ou login perdido pelo proprio runtime.
193
+ const erro = (0, codex_controller_1.erroNativoDoTurno)(p.turn.error);
194
+ state.terminal = { metodo: 'turn/completed', threadId: p.threadId, turnId: p.turn.id, status: p.turn.status,
195
+ ...(erro ? { erro } : {}) };
196
+ state.estado = state.limitacao ? 'unavailable' : p.turn.status === 'interrupted' ? 'stopped' : p.turn.status === 'completed' ? 'completed' : 'failed';
197
+ delete state.bloqueio;
198
+ delete state.perguntaNativa;
199
+ pergunta = null;
200
+ setTimeout(() => child.stdin.end(), 250).unref();
201
+ }
202
+ salvar();
203
+ });
204
+ let ocupado = false;
205
+ const server = net.createServer({ allowHalfOpen: true }, socket => {
206
+ let texto = '';
207
+ socket.setTimeout(11000, () => socket.destroy());
208
+ socket.on('data', b => { texto += b; if (texto.length > 24000)
209
+ socket.destroy(); });
210
+ socket.on('end', async () => {
211
+ if (ocupado) {
212
+ socket.end(JSON.stringify({ ok: false, erro: 'runtime.unavailable: controller ocupado' }));
213
+ return;
214
+ }
215
+ ocupado = true;
216
+ try {
217
+ const r = JSON.parse(texto);
218
+ if (r.instancia !== state.instancia)
219
+ throw new Error('runtime.unavailable: instância divergente');
220
+ let resultado;
221
+ if (r.operacao === 'consultar') {
222
+ const lido = await rpc('thread/read', { threadId: state.sessionId, includeTurns: false });
223
+ if (lido.thread.id !== state.sessionId || lido.thread.cwd !== state.cwd || lido.thread.path !== state.rollout)
224
+ throw new Error('runtime.unavailable: identidade nativa divergente');
225
+ resultado = state;
226
+ }
227
+ else if (r.operacao === 'enviar') {
228
+ const e = r.dados;
229
+ const identidade = e.sessao;
230
+ if (e.contrato !== 'ork.session-answer/v1' || e.thread !== state.vinculo.thread || e.fase !== state.vinculo.fase ||
231
+ identidade.sessionId !== state.sessionId || identidade.runtime !== 'codex' || identidade.cwd !== state.cwd ||
232
+ identidade.instancia !== state.instancia || identidade.bloqueio !== state.bloqueio || !pergunta ||
233
+ pergunta.params.turnId !== state.turno || pergunta.params.questions.length !== 1 ||
234
+ !/^[a-f0-9-]{36}$/.test(e.envioId) || typeof e.resposta !== 'string' || e.resposta.length > 4096)
235
+ throw new Error('runtime.unavailable: entrega não corresponde ao prompt nativo');
236
+ const arquivo = `intent-${e.envioId}.json`;
237
+ if (intencao || fs.existsSync(arquivo))
238
+ throw new Error('runtime.unavailable: envio já iniciado; somente consultar recibo');
239
+ const { resposta, ...semTexto } = e;
240
+ intencao = semTexto;
241
+ gravar(arquivo, intencao); // antes de qualquer escrita no transporte
242
+ const q = pergunta.params.questions[0];
243
+ send({ id: pergunta.id, result: { answers: { [q.id]: { answers: [resposta] } } } });
244
+ await aguardar(() => fs.existsSync(`receipt-${e.envioId}.json`));
245
+ resultado = true;
246
+ }
247
+ else if (r.operacao === 'parar') {
248
+ const s = r.dados;
249
+ if (!pergunta || state.estado !== 'blocked' || s.sessionId !== state.sessionId || s.instancia !== state.instancia ||
250
+ s.cwd !== state.cwd || s.bloqueio !== state.bloqueio)
251
+ throw new Error('runtime.unavailable: sessão não está bloqueada nesta instância');
252
+ await rpc('turn/interrupt', { threadId: state.sessionId, turnId: state.turno });
253
+ await aguardar(() => state.estado === 'stopped');
254
+ resultado = true;
255
+ }
256
+ else
257
+ throw new Error('runtime.unavailable: operação desconhecida');
258
+ socket.end(JSON.stringify({ ok: true, resultado }));
259
+ }
260
+ catch (e) {
261
+ socket.end(JSON.stringify({ ok: false, erro: e.message }));
262
+ }
263
+ finally {
264
+ ocupado = false;
265
+ }
266
+ });
267
+ socket.on('error', () => { });
268
+ });
269
+ child.on('close', (code, signal) => {
270
+ vivo = false;
271
+ clearInterval(vigia);
272
+ for (const p of chamadas.values())
273
+ p.reject(new Error('runtime.unavailable: processo encerrado'));
274
+ chamadas.clear();
275
+ state.processoEncerrado = { em: new Date().toISOString(), code, signal };
276
+ if (!state.terminal) {
277
+ state.estado = 'unavailable';
278
+ state.erro ??= 'runtime.unavailable: processo encerrou sem confirmação';
279
+ }
280
+ salvar();
281
+ server.close();
282
+ });
283
+ child.on('error', () => { if (child.pid === undefined)
284
+ state.runtimeNaoIniciado = true; state.erro = 'runtime.unavailable: falha ao iniciar app-server'; salvar(); server.close(); });
285
+ try {
286
+ if (pedido.contextoRuntime && (pedido.contextoRuntime.host !== 'codex' || pedido.contextoRuntime.threadId !== pedido.vinculo?.thread))
287
+ throw new Error('runtime.context.scope: contexto difere do vinculo do worker');
288
+ const contexto = pedido.contextoRuntime ? (0, runtime_context_1.validarContextoRuntime)(pedido.contextoRuntime, pedido.cwd) : undefined;
289
+ await rpc('initialize', { clientInfo: { name: 'ork_controller', version: '1' }, capabilities: { experimentalApi: true } });
290
+ // O launcher pode passar por env/node via exec mantendo o mesmo processo.
291
+ // Só fixar o executável após a resposta do app-server, sem aceitar outro PID/origem.
292
+ const inicial = state.processoRuntime;
293
+ const pronto = child.pid ? (0, codex_controller_1.identidadeDoProcesso)(child.pid) : undefined;
294
+ if (!inicial || !pronto || ['pid', 'uid', 'inicio', 'bootId', 'cwd'].some(chave => inicial[chave] !== pronto[chave]))
295
+ throw new Error('runtime.unavailable: identidade do runtime mudou durante initialize');
296
+ state.processoRuntime = pronto;
297
+ salvar();
298
+ send({ method: 'initialized' });
299
+ const auth = await rpc('account/read', { refreshToken: false });
300
+ if (auth.account?.type !== 'chatgpt')
301
+ throw new Error('runtime.unavailable: assinatura ChatGPT não comprovada; API paga recusada');
302
+ if (contexto) {
303
+ await rpc('skills/extraRoots/set', { extraRoots: [contexto.instalacao] });
304
+ const lista = await rpc('skills/list', { cwds: [pedido.cwd], forceReload: true });
305
+ const entradas = Array.isArray(lista?.data) ? lista.data.filter((e) => e.cwd === pedido.cwd) : [];
306
+ if (entradas.length !== 1 || !Array.isArray(entradas[0].skills) || !Array.isArray(entradas[0].errors) || entradas[0].errors.length ||
307
+ !entradas[0].skills.some((skill) => skill.name === 'orkastery-bootstrap' && skill.path === contexto.bootstrap && skill.enabled === true))
308
+ throw new Error('runtime.context.discovery: bootstrap instalado nao confirmado no cwd da fase');
309
+ }
310
+ const t = await rpc('thread/start', { cwd: pedido.cwd, model: pedido.model, modelProvider: 'openai',
311
+ sandbox: pedido.sandbox ?? 'workspace-write', approvalPolicy: 'never',
312
+ config: { ...(pedido.effort ? { model_reasoning_effort: pedido.effort } : {}),
313
+ ...(contexto ? { mcp_servers: { orkastery: contexto.servidor } } : {}) } });
314
+ if (t.thread.cwd !== pedido.cwd || !t.thread.path || !/^[a-f0-9-]{36}$/.test(t.thread.id))
315
+ throw new Error('runtime.unavailable: despacho sem identidade');
316
+ state.sessionId = t.thread.id;
317
+ state.rollout = t.thread.path;
318
+ const observado = await rpc('thread/read', { threadId: state.sessionId, includeTurns: false });
319
+ if (observado.thread.id !== state.sessionId || observado.thread.cwd !== state.cwd || observado.thread.path !== state.rollout)
320
+ throw new Error('runtime.unavailable: despacho não confirmado');
321
+ if (contexto) {
322
+ const status = await rpc('mcpServerStatus/list', { threadId: state.sessionId, limit: 100, detail: 'toolsAndAuthOnly' });
323
+ const mcp = Array.isArray(status?.data) ? status.data.filter((s) => s.name === 'orkastery') : [];
324
+ const nomes = mcp.length === 1 && mcp[0].tools && typeof mcp[0].tools === 'object'
325
+ ? Object.values(mcp[0].tools).map((tool) => tool?.name) : [];
326
+ if (!['ork_thread_status', 'ork_observe'].every(nome => nomes.includes(nome)))
327
+ throw new Error('runtime.context.discovery: ferramentas MCP do projeto nao confirmadas');
328
+ gravar('runtime-context.json', { projeto: contexto.projeto, instalacao: contexto.instalacao,
329
+ bootstrap: contexto.bootstrap, cwd: pedido.cwd, sessionId: state.sessionId, ferramentas: nomes });
330
+ }
331
+ await new Promise(resolve => server.listen('control.sock', () => { fs.chmodSync('control.sock', 0o600); resolve(); }));
332
+ const sandboxPolicy = pedido.sandbox === 'read-only' ? { type: 'readOnly' } : pedido.sandbox === 'danger-full-access'
333
+ ? { type: 'dangerFullAccess' } : { type: 'workspaceWrite', writableRoots: [pedido.cwd], networkAccess: false, excludeTmpdirEnvVar: false, excludeSlashTmp: false };
334
+ const reviewBase = pedido.reviewBaseBranch;
335
+ if (reviewBase !== undefined && (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/.test(reviewBase) || reviewBase.includes('..') || reviewBase.endsWith('/')))
336
+ throw new Error('runtime.unavailable: branch base invalida para review');
337
+ const turn = reviewBase
338
+ ? await rpc('review/start', { threadId: state.sessionId, target: { type: 'baseBranch', branch: reviewBase }, delivery: 'inline' })
339
+ : await rpc('turn/start', { threadId: state.sessionId, model: pedido.model ?? t.model, approvalPolicy: 'never', sandboxPolicy,
340
+ input: [{ type: 'text', text: pedido.prompt, text_elements: [] }], effort: pedido.effort,
341
+ ...(pedido.outputSchema ? { outputSchema: pedido.outputSchema } : {}),
342
+ ...(pedido.colaboracao ? { collaborationMode: { mode: pedido.colaboracao, settings: { model: t.model, reasoning_effort: pedido.effort ?? null, developer_instructions: null } } } : {}) });
343
+ if (reviewBase && (!turn?.turn?.id || typeof turn.turn.id !== 'string'))
344
+ throw new Error('runtime.unavailable: review nativo sem identidade de turno');
345
+ if (reviewBase && turn.reviewThreadId !== state.sessionId)
346
+ throw new Error('runtime.unavailable: review inline mudou a identidade da thread');
347
+ state.turno = turn.turn.id;
348
+ state.pronto = true;
349
+ salvar();
350
+ }
351
+ catch (e) {
352
+ state.estado = 'unavailable';
353
+ state.erro = e.message;
354
+ salvar();
355
+ child.stdin.end();
356
+ server.close();
357
+ }
358
+ }
359
+ if (require.main === module) {
360
+ (process.argv[2] === '--rpc' ? cliente() : servidor()).catch(() => { process.exitCode = 1; });
361
+ }
@@ -0,0 +1,252 @@
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.LIMITE_MENSAGEM_ERRO_NATIVO = void 0;
37
+ exports.identidadeDoProcesso = identidadeDoProcesso;
38
+ exports.processoTerminou = processoTerminou;
39
+ exports.erroNativoDoTurno = erroNativoDoTurno;
40
+ exports.erroNativoValido = erroNativoValido;
41
+ exports.conferirDiretorio = conferirDiretorio;
42
+ exports.lerEstadoController = lerEstadoController;
43
+ exports.controleDoController = controleDoController;
44
+ exports.despacharComController = despacharComController;
45
+ exports.encerrarController = encerrarController;
46
+ /** Controller criado pelo despacho, nunca descoberto por PID ou resume de outra sessão. */
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ const node_child_process_1 = require("node:child_process");
50
+ const node_crypto_1 = require("node:crypto");
51
+ const playbook_contracts_1 = require("../playbook-contracts");
52
+ /** Só captura PIDs obtidos do spawn próprio ou do próprio worker, nunca descoberta por nome. */
53
+ function identidadeDoProcesso(pid) {
54
+ const base = `/proc/${pid}`, st = fs.readFileSync(`${base}/stat`, 'utf8');
55
+ return { pid, uid: fs.statSync(base).uid, inicio: st.slice(st.lastIndexOf(')') + 2).split(' ')[19],
56
+ bootId: fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim(), executavel: fs.readlinkSync(`${base}/exe`), cwd: fs.readlinkSync(`${base}/cwd`) };
57
+ }
58
+ function processoTerminou(p) {
59
+ if (!p)
60
+ return false;
61
+ try {
62
+ const st = fs.readFileSync(`/proc/${p.pid}/stat`, 'utf8'), campos = st.slice(st.lastIndexOf(')') + 2).split(' ');
63
+ const boot = fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim();
64
+ // Zombie é terminal, embora /proc ainda espere o reaper; não existe sinalização aqui.
65
+ return boot !== p.bootId || campos[19] !== p.inicio || campos[0] === 'Z';
66
+ }
67
+ catch (e) {
68
+ return e.code === 'ENOENT';
69
+ }
70
+ }
71
+ exports.LIMITE_MENSAGEM_ERRO_NATIVO = 300;
72
+ const CODIGO_ERRO_NATIVO = /^[A-Za-z0-9_]{1,64}$/;
73
+ function erroNativoDoTurno(v) {
74
+ if (!v || typeof v !== 'object' || Array.isArray(v))
75
+ return null;
76
+ const o = v;
77
+ const info = o.codexErrorInfo;
78
+ const bruto = typeof info === 'string' ? info : info && typeof info === 'object' && !Array.isArray(info) ? Object.keys(info)[0] ?? '' : '';
79
+ const codigo = bruto.replace(/[^A-Za-z0-9_]/g, '').slice(0, 64) || null;
80
+ const mensagem = typeof o.message === 'string'
81
+ ? Array.from(o.message.replace(/\p{Cc}/gu, ' ')).slice(0, exports.LIMITE_MENSAGEM_ERRO_NATIVO).join('').trim() : '';
82
+ return codigo || mensagem ? { codigo, mensagem } : null;
83
+ }
84
+ /** Forma exata gravada pelo worker; qualquer outra coisa no `state.json` e recusada pelo sensor. */
85
+ function erroNativoValido(v) {
86
+ if (!v || typeof v !== 'object' || Array.isArray(v))
87
+ return false;
88
+ const o = v;
89
+ return Object.keys(o).every(k => k === 'codigo' || k === 'mensagem') &&
90
+ (o.codigo === null || (typeof o.codigo === 'string' && CODIGO_ERRO_NATIVO.test(o.codigo))) &&
91
+ typeof o.mensagem === 'string' && Array.from(o.mensagem).length <= exports.LIMITE_MENSAGEM_ERRO_NATIVO && !/\p{Cc}/u.test(o.mensagem) &&
92
+ (o.codigo !== null || o.mensagem !== '');
93
+ }
94
+ const worker = () => path.join(__dirname, 'codex-controller-worker.js');
95
+ function conferirDiretorio(dir) {
96
+ const st = fs.lstatSync(dir);
97
+ if (!st.isDirectory() || st.isSymbolicLink() || st.uid !== process.getuid?.() || (st.mode & 0o077)) {
98
+ throw new Error('runtime.unavailable: IPC exige diretório privado 0700 do uid autenticado');
99
+ }
100
+ }
101
+ function lerEstadoController(dir) {
102
+ conferirDiretorio(dir);
103
+ return JSON.parse(fs.readFileSync(path.join(dir, 'state.json'), 'utf8'));
104
+ }
105
+ function rpcController(dir, instancia, operacao, dados, timeout = 12000) {
106
+ conferirDiretorio(dir);
107
+ const r = (0, node_child_process_1.spawnSync)(process.execPath, [worker(), '--rpc'], { cwd: dir, encoding: 'utf8', timeout: Math.max(1, Math.floor(timeout)),
108
+ input: JSON.stringify({ instancia, operacao, dados }), maxBuffer: 1024 * 1024 });
109
+ if (r.status !== 0)
110
+ throw new Error('runtime.unavailable: controller não respondeu; não reenviar');
111
+ const out = JSON.parse(r.stdout);
112
+ if (!out.ok)
113
+ throw new Error(out.erro ?? 'runtime.unavailable: controller recusou');
114
+ return out.resultado;
115
+ }
116
+ function controleDoController(dir, vinculo, sessionId, cwd) {
117
+ const inicial = lerEstadoController(dir);
118
+ if (inicial.sessionId !== sessionId || inicial.cwd !== cwd ||
119
+ JSON.stringify(inicial.vinculo) !== JSON.stringify(vinculo))
120
+ throw new Error('runtime.unavailable: vínculo de despacho divergente');
121
+ const consultar = (orcamentoMs = 12000) => {
122
+ try {
123
+ const salvo = lerEstadoController(dir);
124
+ if (!salvo.terminal && !salvo.processoEncerrado && orcamentoMs <= 0)
125
+ return { ok: false, sessoes: [] };
126
+ const s = salvo.terminal || salvo.processoEncerrado ? salvo : rpcController(dir, inicial.instancia, 'consultar', undefined, Math.min(12000, orcamentoMs));
127
+ if (s.instancia !== inicial.instancia || s.sessionId !== sessionId || s.cwd !== cwd)
128
+ return { ok: false, sessoes: [] };
129
+ return { ok: true, sessoes: [{ sessionId, runtime: 'codex', cwd, instancia: s.instancia,
130
+ estado: s.estado, bloqueio: s.bloqueio, ...{ perguntaNativa: s.perguntaNativa, limitacao: s.limitacao } }] };
131
+ }
132
+ catch {
133
+ return { ok: false, sessoes: [] };
134
+ }
135
+ };
136
+ return { consultar,
137
+ enviar: e => { rpcController(dir, inicial.instancia, 'enviar', e); },
138
+ confirmar: e => {
139
+ if (!/^[a-f0-9-]{36}$/.test(e.envioId))
140
+ return null;
141
+ const file = path.join(dir, `receipt-${e.envioId}.json`);
142
+ return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : null;
143
+ },
144
+ parar: s => { try {
145
+ rpcController(dir, inicial.instancia, 'parar', s);
146
+ return consultar().sessoes[0]?.estado === 'stopped';
147
+ }
148
+ catch {
149
+ return false;
150
+ } }, };
151
+ }
152
+ function despacharComController(pedido, env) {
153
+ const comando = ['codex', 'app-server', '--listen', 'stdio://'];
154
+ const vazio = { comando, sessionId: null, verificada: false, stdout: '', stderr: '' };
155
+ if (!pedido.vinculo || (0, node_crypto_1.createHash)('sha256').update(pedido.prompt).digest('hex') !== pedido.vinculo.promptSha256) {
156
+ return { ...vazio, ok: false, erro: 'runtime.unavailable: prompt não corresponde ao vínculo do despacho' };
157
+ }
158
+ // I-33 (D2): o worker herda este env inteiro; ele precisa carregar o CODEX_HOME do perfil do
159
+ // pedido, senao o turno rodaria e seria reverificado por outra conta.
160
+ if (pedido.perfil && env.CODEX_HOME !== pedido.perfil.codexHome)
161
+ return { ...vazio, ok: false, erro: 'runtime.unavailable: CODEX_HOME do despacho diverge do perfil do pedido' };
162
+ if (pedido.outputSchema !== undefined) {
163
+ try {
164
+ (0, playbook_contracts_1.validarSchemaPlaybook)(pedido.outputSchema);
165
+ }
166
+ catch {
167
+ return { ...vazio, ok: false, erro: 'runtime.unavailable: output schema invalido' };
168
+ }
169
+ }
170
+ if (pedido.dryRun)
171
+ return { ...vazio, ok: true };
172
+ const duracaoMaximaMs = pedido.duracaoMaximaMs ?? 6 * 60 * 60 * 1000;
173
+ if (!Number.isSafeInteger(duracaoMaximaMs) || duracaoMaximaMs < 1 || duracaoMaximaMs > 24 * 60 * 60 * 1000)
174
+ return { ...vazio, ok: false, erro: 'runtime.unavailable: duração máxima inválida' };
175
+ const instancia = (0, node_crypto_1.randomUUID)();
176
+ const limite = Date.now() + (pedido.esperaMs ?? 30000);
177
+ const dir = path.join(pedido.logDir ?? pedido.cwd, `controller-${(0, node_crypto_1.randomUUID)()}`);
178
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
179
+ const bootstrap = path.join(dir, 'bootstrap.json');
180
+ fs.writeFileSync(path.join(dir, 'launch.json'), JSON.stringify({ contrato: 'ork.controller-launch/v1', instancia, vinculo: pedido.vinculo, cwd: pedido.cwd, criadoEm: new Date().toISOString() }), { flag: 'wx', mode: 0o600 });
181
+ fs.writeFileSync(bootstrap, JSON.stringify({ pedido, instancia, prazoInicial: new Date(limite).toISOString(), duracaoMaximaMs }), { mode: 0o600, flag: 'wx' });
182
+ const fd = fs.openSync(bootstrap, 'r');
183
+ try {
184
+ const child = (0, node_child_process_1.spawn)(process.execPath, [worker()], { cwd: dir, env, detached: true, stdio: [fd, 'ignore', 'ignore'] });
185
+ child.on('error', () => { });
186
+ if (child.pid) {
187
+ let processoController;
188
+ try {
189
+ processoController = identidadeDoProcesso(child.pid);
190
+ }
191
+ catch { /* worker pode ter saído; referência nunca é perdida */ }
192
+ fs.writeFileSync(path.join(dir, 'process-launch.json'), JSON.stringify({ instancia, pid: child.pid, processoController }), { mode: 0o600, flag: 'wx' });
193
+ }
194
+ child.unref();
195
+ }
196
+ finally {
197
+ fs.closeSync(fd);
198
+ fs.unlinkSync(bootstrap);
199
+ }
200
+ while (Date.now() < limite) {
201
+ if (fs.existsSync(path.join(dir, 'state.json'))) {
202
+ const s = lerEstadoController(dir);
203
+ if (s.erro)
204
+ return { ...vazio, ok: false, erro: s.erro, controlador: dir };
205
+ if (s.pronto)
206
+ return { ...vazio, ok: true, sessionId: s.sessionId, verificada: true, controlador: dir };
207
+ }
208
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
209
+ }
210
+ solicitarEncerramento(dir, instancia);
211
+ return { ...vazio, ok: false, erro: 'runtime.unavailable: controller sem confirmação do despacho; encerramento solicitado, referência preservada', controlador: dir };
212
+ }
213
+ function solicitarEncerramento(dir, instancia) {
214
+ const file = path.join(dir, 'stop-request.json'), value = { instancia, solicitadoEm: new Date().toISOString() };
215
+ try {
216
+ fs.writeFileSync(file, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
217
+ }
218
+ catch (e) {
219
+ if (e.code !== 'EEXIST')
220
+ throw e;
221
+ if (JSON.parse(fs.readFileSync(file, 'utf8')).instancia !== instancia)
222
+ throw new Error('runtime.unavailable: pedido de encerramento divergente');
223
+ }
224
+ }
225
+ /** Recuperação exata por referência do despacho. Nunca signal/resume/descoberta de sessão. */
226
+ function encerrarController(dir, vinculo, instancia, esperaMs = 10000) {
227
+ conferirDiretorio(dir);
228
+ const launch = JSON.parse(fs.readFileSync(path.join(dir, 'launch.json'), 'utf8'));
229
+ if (launch.instancia !== instancia || JSON.stringify(launch.vinculo) !== JSON.stringify(vinculo))
230
+ throw new Error('runtime.unavailable: vínculo do encerramento divergente');
231
+ solicitarEncerramento(dir, instancia);
232
+ const fim = performance.now() + esperaMs;
233
+ let state;
234
+ do {
235
+ try {
236
+ state = lerEstadoController(dir);
237
+ }
238
+ catch { /* bootstrap ainda não observado */ }
239
+ if (state && state.instancia !== instancia)
240
+ throw new Error('runtime.unavailable: instância do encerramento divergente');
241
+ if (state && processoTerminou(state.processoController) && (processoTerminou(state.processoRuntime) || state.runtimeNaoIniciado === true && state.runtimePid === undefined && !!state.processoEncerrado)) {
242
+ const result = { ok: true, instancia, sessionId: state.sessionId, processoController: state.processoController,
243
+ processoRuntime: state.processoRuntime, runtimeNaoIniciado: state.runtimeNaoIniciado === true,
244
+ evidencia: state.runtimeNaoIniciado ? 'worker capturado terminal; ChildProcess confirmou falha de spawn sem PID de runtime' : 'ambas identidades capturadas no spawn estão terminais; sem sinal genérico', observadoEm: new Date().toISOString() };
245
+ fs.writeFileSync(path.join(dir, 'processes-ended.json'), JSON.stringify(result), { mode: 0o600 });
246
+ return result;
247
+ }
248
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
249
+ } while (performance.now() < fim);
250
+ return { ok: false, instancia, sessionId: state?.sessionId ?? null, controlador: dir,
251
+ evidencia: 'runtime.unavailable: encerramento pendente; referência preservada para inspeção, nenhum sinal ou resume' };
252
+ }