@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
package/dist/hitl.js ADDED
@@ -0,0 +1,711 @@
1
+ "use strict";
2
+ /**
3
+ * `ork sessions hitl`: radar de sessoes do runtime que exigem acao humana AGORA.
4
+ *
5
+ * O incidente que este arquivo resolve (05/09/2026): uma sessao `claude --bg` da Factory
6
+ * ficou parada em `state: blocked` pedindo o codigo 2FA do `npm publish`, e o
7
+ * orquestrador so descobriu quando o humano reclamou. O `ork monitor` nao pegou porque
8
+ * ele e THREAD-centrico: ele deriva paradas de `thread.json` e `ledger.jsonl`, e aquela
9
+ * sessao nao tinha thread. Pior: para o monitor, uma sessao que aparece no
10
+ * `claude agents` conta como VIVA, entao uma sessao travada era lida como uma sessao
11
+ * trabalhando -- um falso negativo que atrasa roadmap.
12
+ *
13
+ * Este radar cobre o outro lado: ele parte das SESSOES do runtime, e nao das threads.
14
+ *
15
+ * Tres regras que mandam aqui:
16
+ *
17
+ * 1. **Nada de estado novo.** O radar le `claude agents --json --all` e, so para as
18
+ * sessoes paradas, o `claude logs` daquela sessao. Nao grava nada, entao e seguro
19
+ * rodar em laco e funciona em sessao criada por qualquer um, dentro ou fora do ork.
20
+ * 2. **Estado desconhecido avisa A MAIS.** Um `state` que o runtime inventar amanha cai
21
+ * em `desconhecida` e conta como "precisa de humano". Perder uma pausa custa
22
+ * roadmap; avisar de uma pausa que nao existe custa uma linha de tabela.
23
+ * 3. **Fonte declarada.** O runtime nao diz DESDE QUANDO a sessao esta bloqueada, so
24
+ * quando ela comecou. Entao o campo se chama `idadeMin` e o texto diz que e idade da
25
+ * sessao. Chutar o instante do bloqueio seria inventar dado.
26
+ *
27
+ * E um `state: blocked` NAO e uma coisa so. MEDIDO na maquina em 05/09/2026, das 8
28
+ * sessoes `blocked` apenas 2 tinham job vivo: nas outras 6 o `claude logs` respondia
29
+ * "job not found" com codigo 1, ou seja, o estado ficou carimbado depois de o job sair.
30
+ * Tratar as duas como a mesma coisa daria 6 alarmes falsos para 2 verdadeiros, e alarme
31
+ * falso cronico e como o humano aprende a ignorar o radar. Por isso a sessao `blocked`
32
+ * com job vivo e `hitl` (responda o prompt agora) e a sem job vivo e `abandonada`
33
+ * (morreu esperando resposta, o trabalho ficou pela metade e alguem decide o que fazer).
34
+ */
35
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
36
+ if (k2 === undefined) k2 = k;
37
+ var desc = Object.getOwnPropertyDescriptor(m, k);
38
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
39
+ desc = { enumerable: true, get: function() { return m[k]; } };
40
+ }
41
+ Object.defineProperty(o, k2, desc);
42
+ }) : (function(o, m, k, k2) {
43
+ if (k2 === undefined) k2 = k;
44
+ o[k2] = m[k];
45
+ }));
46
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
47
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
48
+ }) : function(o, v) {
49
+ o["default"] = v;
50
+ });
51
+ var __importStar = (this && this.__importStar) || (function () {
52
+ var ownKeys = function(o) {
53
+ ownKeys = Object.getOwnPropertyNames || function (o) {
54
+ var ar = [];
55
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
56
+ return ar;
57
+ };
58
+ return ownKeys(o);
59
+ };
60
+ return function (mod) {
61
+ if (mod && mod.__esModule) return mod;
62
+ var result = {};
63
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
64
+ __setModuleDefault(result, mod);
65
+ return result;
66
+ };
67
+ })();
68
+ Object.defineProperty(exports, "__esModule", { value: true });
69
+ exports.ORCAMENTO_LOGS_PADRAO_MS = exports.duracaoCurta = exports.ATENCAO_PADRAO_MIN = void 0;
70
+ exports.redigirSegredos = redigirSegredos;
71
+ exports.contextoSeguro = contextoSeguro;
72
+ exports.estadoBruto = estadoBruto;
73
+ exports.classificarEstado = classificarEstado;
74
+ exports.lerPerguntaDaTela = lerPerguntaDaTela;
75
+ exports.recomendarPara = recomendarPara;
76
+ exports.lerSessoesEncerradas = lerSessoesEncerradas;
77
+ exports.varrerSessoes = varrerSessoes;
78
+ exports.textoDoRadar = textoDoRadar;
79
+ const fs = __importStar(require("node:fs"));
80
+ const path = __importStar(require("node:path"));
81
+ const adapter = __importStar(require("./adapters/claude-bg"));
82
+ const estado_thread_1 = require("./estado-thread");
83
+ const hitl_sessions_1 = require("./hitl-sessions");
84
+ const thread_1 = require("./thread");
85
+ const ledger_1 = require("./ledger");
86
+ const util_1 = require("./util");
87
+ const horario_1 = require("./horario");
88
+ Object.defineProperty(exports, "duracaoCurta", { enumerable: true, get: function () { return horario_1.duracaoCurta; } });
89
+ const hitl_estado_1 = require("./hitl-estado");
90
+ const redacao_url_1 = require("./redacao-url");
91
+ /** Limite de atencao padrao, em minutos, igual ao do monitor de orquestracao. */
92
+ exports.ATENCAO_PADRAO_MIN = 30;
93
+ function redigirSegredos(texto) {
94
+ return (0, redacao_url_1.redigirCredenciaisUrl)(texto
95
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
96
+ .replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ''))
97
+ .replace(/-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----[\s\S]*?(?:-----END (?:[A-Z0-9]+ )*PRIVATE KEY-----|$)/g, '[chave privada redigida]')
98
+ .replace(/\b(?:sk-(?:ant-)?[\w-]{16,}|gh[pousr]_[\w]{20,}|AKIA[A-Z0-9]{16})\b/g, '[redigido]')
99
+ .replace(/\b(?:\d{5,15}:[A-Za-z0-9_-]{20,}|AIza[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g, '[redigido]')
100
+ .replace(/(authorization\s*[:=]\s*)(?:Bearer|Basic)\s+[^\s,;]+/gi, '$1[redigido]')
101
+ .replace(/\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, '[redigido]')
102
+ .replace(/((?:token|password|senha|secret|api[_-]?key|authorization)\s*[:=]\s*)[^\s,]+/gi, '$1[redigido]');
103
+ }
104
+ function contextoSeguro(texto, linhas = 20) {
105
+ return redigirSegredos(texto).split('\n').filter(l => l.trim()).slice(-linhas).map(l => l.slice(0, 400));
106
+ }
107
+ /**
108
+ * Estados observados do `claude agents --json --all` em 05/09/2026 (versao 2.1.259).
109
+ *
110
+ * `stopped` NAO pede humano: a sessao foi parada por decisao de alguem. `failed` pede,
111
+ * porque alguem precisa decidir entre retomar, corrigir ou abandonar.
112
+ */
113
+ const REGRA_POR_ESTADO = {
114
+ blocked: {
115
+ classe: 'hitl',
116
+ precisaDeHumano: true,
117
+ detalhe: 'sessao parada esperando acao humana (prompt, permissao ou credencial)',
118
+ },
119
+ failed: {
120
+ classe: 'falha',
121
+ precisaDeHumano: true,
122
+ detalhe: 'sessao terminou em erro: alguem decide entre retomar, corrigir ou abandonar',
123
+ },
124
+ working: { classe: 'trabalhando', precisaDeHumano: false, detalhe: 'sessao em execucao' },
125
+ done: { classe: 'concluida', precisaDeHumano: false, detalhe: 'sessao concluiu' },
126
+ stopped: {
127
+ classe: 'interrompida',
128
+ precisaDeHumano: false,
129
+ detalhe: 'sessao parada por decisao humana (`claude stop`)',
130
+ },
131
+ idle: {
132
+ classe: 'interativa',
133
+ precisaDeHumano: false,
134
+ detalhe: 'sessao interativa ociosa, conduzida por um humano no terminal',
135
+ },
136
+ };
137
+ /** Estado cru da sessao: `state` manda, `status` e o fallback historico. */
138
+ function estadoBruto(s) {
139
+ const bruto = s.state ?? s.status ?? '';
140
+ return typeof bruto === 'string' ? bruto.trim().toLowerCase() : '';
141
+ }
142
+ /** Classifica o estado cru do runtime. Estado que nao esta no mapa avisa a mais. */
143
+ function classificarEstado(estado) {
144
+ const regra = REGRA_POR_ESTADO[estado];
145
+ if (regra)
146
+ return regra;
147
+ return {
148
+ classe: 'desconhecida',
149
+ precisaDeHumano: true,
150
+ detalhe: estado === ''
151
+ ? 'runtime nao informou estado desta sessao: tratada como possivel parada'
152
+ : `estado "${estado}" nao esta no catalogo do ork: tratado como possivel parada`,
153
+ };
154
+ }
155
+ /** Resposta do `claude logs` quando o job da sessao ja saiu do runtime. */
156
+ const JOB_SUMIU = /job not found|may have already exited|no job matching/i;
157
+ /**
158
+ * I-45: nesta VPS o `claude logs` de sessao morta responde `connect ENOENT .../control.sock`: o
159
+ * daemon que controlava o job nao existe mais. So o cache usa este sinal; a confirmacao de
160
+ * superacao continua exigindo a frase de job ausente, como antes.
161
+ */
162
+ const SOCKET_AUSENTE = /connect ENOENT \S*control\.sock/i;
163
+ /** Linha de menu do runtime (`1. Passo o codigo agora`, `❯2.Eu mesmo publico`). */
164
+ const OPCAO = /^[^\w\d]{0,4}(\d{1,2})[.)]\s*(\S.*)$/;
165
+ /**
166
+ * Item de menu de SELECAO (`❯ [✔] codegraph`, `[ ] outro`), sem numero nenhum.
167
+ *
168
+ * MEDIDO na maquina em 05/09/2026: as duas sessoes `blocked` com job vivo estavam
169
+ * paradas exatamente neste formato -- o prompt de habilitar servidores MCP. Enxergar so
170
+ * o menu numerado deixava a fila do humano sem pergunta e sem alternativa, que e o dado
171
+ * pelo qual o radar existe.
172
+ */
173
+ const OPCAO_MARCADA = /^[^\w\d]{0,4}\[([ xX✔✓*])\]\s*(\S.*)$/;
174
+ /** Casa uma linha de menu nos dois formatos e devolve o rotulo ja normalizado. */
175
+ function casarOpcao(linha) {
176
+ const numerada = linha.match(OPCAO);
177
+ if (numerada)
178
+ return `${numerada[1]}. ${numerada[2]}`;
179
+ const marcada = linha.match(OPCAO_MARCADA);
180
+ if (marcada)
181
+ return `[${marcada[1].trim() === '' ? ' ' : 'x'}] ${marcada[2]}`;
182
+ return null;
183
+ }
184
+ /** Rodapes e molduras do dump de tela que nunca sao pergunta. */
185
+ const RUIDO = /^[\s─│|>❯·]*$/;
186
+ /** Palavras que denunciam pedido de segredo. O caso 2FA do incidente cai aqui. */
187
+ const CREDENCIAL = /\b(2fa|otp|mfa|token|senha|password|credenc|autentic|authenticat|login|api[- ]?key|secret)\b/i;
188
+ /** Palavras do prompt de permissao do proprio Claude Code. */
189
+ const PERMISSAO = /\b(do you want to proceed|allow this|permitir|permission|grant access|trust this|requires? approval|new mcp servers?|wish to enable|to reject all)\b/i;
190
+ /** Ultimo bloco de opcoes NUMERADAS da tela: e o menu que esta aberto agora. */
191
+ function lerMenuNumerado(linhas) {
192
+ let fim = -1;
193
+ for (let i = linhas.length - 1; i >= 0; i--) {
194
+ if (OPCAO.test(linhas[i])) {
195
+ fim = i;
196
+ break;
197
+ }
198
+ }
199
+ if (fim < 0)
200
+ return { alternativas: [], inicio: -1, fim: -1 };
201
+ // Sobe enquanto as linhas ainda pertencem ao menu (opcoes e as descricoes delas).
202
+ const alternativas = [];
203
+ let inicio = fim;
204
+ for (let i = fim; i >= 0 && fim - i < 40; i--) {
205
+ const rotulo = linhas[i].match(OPCAO) ? casarOpcao(linhas[i]) : null;
206
+ if (rotulo) {
207
+ alternativas.unshift(rotulo);
208
+ inicio = i;
209
+ }
210
+ }
211
+ return { alternativas, inicio, fim };
212
+ }
213
+ /**
214
+ * Ultimo bloco CONTIGUO de opcoes marcadas (`[✔] codegraph`).
215
+ *
216
+ * A contiguidade e o que separa um menu de uma lista de tarefas: o Claude Code desenha
217
+ * TODO com caixinha tambem, e uma varredura frouxa acabaria oferecendo tarefa concluida
218
+ * como se fosse alternativa de resposta.
219
+ */
220
+ function lerMenuMarcado(linhas) {
221
+ let fim = -1;
222
+ for (let i = linhas.length - 1; i >= 0; i--) {
223
+ if (OPCAO_MARCADA.test(linhas[i])) {
224
+ fim = i;
225
+ break;
226
+ }
227
+ }
228
+ if (fim < 0)
229
+ return { alternativas: [], inicio: -1, fim: -1 };
230
+ const alternativas = [];
231
+ let inicio = fim;
232
+ for (let i = fim; i >= 0; i--) {
233
+ const rotulo = OPCAO_MARCADA.test(linhas[i]) ? casarOpcao(linhas[i]) : null;
234
+ if (!rotulo)
235
+ break;
236
+ alternativas.unshift(rotulo);
237
+ inicio = i;
238
+ }
239
+ return { alternativas, inicio, fim };
240
+ }
241
+ function lerPerguntaDaTela(textoLimpo) {
242
+ const linhas = textoLimpo.split('\n').map((l) => l.trim()).filter((l) => l !== '');
243
+ // O menu numerado manda: e o formato mais comum e o mais inequivoco. O menu de selecao
244
+ // so entra quando nao ha nenhum numerado na tela.
245
+ let { alternativas, inicio, fim } = lerMenuNumerado(linhas);
246
+ if (fim < 0)
247
+ ({ alternativas, inicio, fim } = lerMenuMarcado(linhas));
248
+ if (fim < 0) {
249
+ const tipo = CREDENCIAL.test(textoLimpo)
250
+ ? 'hitl.credencial'
251
+ : PERMISSAO.test(textoLimpo)
252
+ ? 'hitl.permissao'
253
+ : 'hitl.desconhecido';
254
+ return { pergunta: '', alternativas: [], tipo };
255
+ }
256
+ // A pergunta e a ultima linha de texto de verdade ANTES da primeira opcao.
257
+ let pergunta = '';
258
+ for (let i = inicio - 1; i >= 0 && inicio - i < 8; i--) {
259
+ const l = linhas[i].replace(/^[│|>❯\s]+/, '').trim();
260
+ if (RUIDO.test(l) || l.length < 12)
261
+ continue;
262
+ pergunta = l;
263
+ break;
264
+ }
265
+ const alvo = `${pergunta}\n${alternativas.join('\n')}`;
266
+ const tipo = CREDENCIAL.test(alvo)
267
+ ? 'hitl.credencial'
268
+ : PERMISSAO.test(alvo)
269
+ ? 'hitl.permissao'
270
+ : 'hitl.pergunta';
271
+ return { pergunta, alternativas, tipo };
272
+ }
273
+ /**
274
+ * A recomendacao unica para cada tipo de parada.
275
+ *
276
+ * Deterministica de proposito: ela responde "o que eu faria", que e o que a regra de
277
+ * conducao exige junto das alternativas, sem precisar de LLM nenhum no nucleo. Quando a
278
+ * sessao ofereceu opcoes proprias, elas vao junto no campo `alternativas` -- o `ork` nao
279
+ * inventa opcao que a sessao nao deu.
280
+ */
281
+ function recomendarPara(classe, tipo, id) {
282
+ if (classe === 'abandonada') {
283
+ return ('nao responda: o job ja saiu. Confira o que ficou na worktree da sessao e ' +
284
+ 'redespache so o resto do trabalho.');
285
+ }
286
+ if (classe === 'falha') {
287
+ return `leia \`ork sessions logs ${id}\` e decida entre retomar, corrigir a causa ou abandonar a sessao.`;
288
+ }
289
+ if (classe === 'desconhecida') {
290
+ return `estado fora do catalogo: confirme no \`ork sessions logs ${id}\` se a sessao esta mesmo parada.`;
291
+ }
292
+ switch (tipo) {
293
+ case 'hitl.credencial':
294
+ return (`entregue o segredo agora (\`claude attach ${id}\`) para nao segurar a entrega; ` +
295
+ 'se este mesmo bloqueio ja se repetiu, troque por credencial de automacao e ele nao volta.');
296
+ case 'hitl.permissao':
297
+ return (`responda o prompt (\`claude attach ${id}\`); se o comando e rotineiro nesse projeto, ` +
298
+ 'coloque-o na allowlist de permissoes para nao parar de novo pelo mesmo motivo.');
299
+ case 'hitl.pergunta':
300
+ return `abra a sessao (\`claude attach ${id}\`) e escolha entre as opcoes que ela listou.`;
301
+ default:
302
+ return `veja o que a sessao pede com \`ork sessions logs ${id}\` antes de responder.`;
303
+ }
304
+ }
305
+ /** Minutos inteiros entre um epoch em ms e um carimbo ISO (nunca negativo). */
306
+ function idadeEmMinutos(inicioMs, ateEm) {
307
+ const fim = Date.parse(ateEm);
308
+ if (!Number.isFinite(inicioMs) || !Number.isFinite(fim))
309
+ return 0;
310
+ return Math.max(0, Math.floor((fim - inicioMs) / 60000));
311
+ }
312
+ function sessoesRegistradas(raiz) {
313
+ const mapa = new Map(), ambiguas = new Set();
314
+ if (raiz === '')
315
+ return mapa;
316
+ let ids;
317
+ try {
318
+ ids = (0, thread_1.listarIds)(raiz);
319
+ }
320
+ catch {
321
+ return mapa;
322
+ }
323
+ for (const id of ids) {
324
+ try {
325
+ const t = (0, thread_1.lerThread)(raiz, id);
326
+ for (const s of t.sessoes) {
327
+ const anterior = mapa.get(s.sessionId);
328
+ if (ambiguas.has(s.sessionId))
329
+ continue;
330
+ if (anterior && (anterior.id !== t.id || anterior.fase !== s.fase || anterior.runtime !== s.runtime)) {
331
+ mapa.delete(s.sessionId);
332
+ ambiguas.add(s.sessionId);
333
+ continue;
334
+ }
335
+ mapa.set(s.sessionId, { id: t.id, fase: s.fase, slug: s.slug, runtime: s.runtime, controlador: s.controlador, desdeEm: s.despachadaEm });
336
+ }
337
+ }
338
+ catch {
339
+ // Thread ilegivel nao derruba o radar: o radar existe justamente para avisar.
340
+ }
341
+ }
342
+ return mapa;
343
+ }
344
+ /** Recibo não sobrepõe job vivo ou falha de consulta; só resolve resíduo sem job. */
345
+ function temSuperacaoConfirmada(s, registro, eventos) {
346
+ if (!registro || registro.runtime !== 'claude-bg' || !s.id || !s.cwd || !Number.isFinite(s.startedAt))
347
+ return false;
348
+ const instancia = JSON.stringify([s.id, s.startedAt]);
349
+ const corresponde = (e) => e.thread === registro.id && e.sessionId === s.sessionId &&
350
+ e.fase === registro.fase && e.runtime === registro.runtime && e.instancia === instancia && e.cwd === s.cwd;
351
+ const recibo = eventos.filter(e => e.tipo === 'session_superseded' && corresponde(e) &&
352
+ ['ausente', 'stopped', 'done', 'completed', 'exited'].includes(String(e.confirmacao))).at(-1);
353
+ if (!recibo)
354
+ return false;
355
+ const i = eventos.indexOf(recibo);
356
+ if (eventos.slice(i + 1).some(e => e.sessionId === s.sessionId &&
357
+ ['phase_dispatch', 'sessao_bloqueada', 'session_answer_sending', 'session_superseding'].includes(e.tipo) ||
358
+ e.tipo === 'hitl_requested' && e.pedido?.alvo?.sessionId === s.sessionId))
359
+ return false;
360
+ const antes = eventos.slice(0, i);
361
+ const tentativa = antes.filter(e => e.tipo === 'session_superseding' && corresponde(e) && e.superadaPor === recibo.superadaPor).at(-1);
362
+ if (!tentativa || typeof recibo.superadaPor !== 'string' || recibo.superadaPor === s.sessionId)
363
+ return false;
364
+ const despachos = antes.slice(0, antes.indexOf(tentativa));
365
+ const original = despachos.findIndex(e => e.tipo === 'phase_dispatch' && e.sessionId === s.sessionId &&
366
+ e.fase === registro.fase && e.runtime === registro.runtime);
367
+ const posterior = despachos.findIndex((e, n) => n > original && e.tipo === 'phase_dispatch' &&
368
+ e.sessionId === recibo.superadaPor && e.fase === registro.fase);
369
+ return original >= 0 && posterior > original && despachos.slice(posterior + 1).some(e => e.tipo === 'phase_dispatch_verified' && e.sessionId === recibo.superadaPor && e.encontrada === true);
370
+ }
371
+ /**
372
+ * I-45: sessao cujo job o runtime ja declarou ausente nao volta. Ler a tela dela de novo custa um
373
+ * `claude agents logs` por varredura (7,9 s medidos nesta VPS em 25/09/2026, com a CPU roubada)
374
+ * para descobrir o que ja se sabe. O cache guarda so esse fato negativo, por sessionId, e e gravado
375
+ * na hora em que ele e visto: uma varredura cortada pelo teto nao perde o que ja leu.
376
+ */
377
+ exports.ORCAMENTO_LOGS_PADRAO_MS = 120_000;
378
+ const ARQUIVO_DE_ENCERRADAS = 'radar-encerradas.json';
379
+ function caminhoDeEncerradas(raiz) {
380
+ return path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor', ARQUIVO_DE_ENCERRADAS);
381
+ }
382
+ /** `sessionId|inicio da instancia` -> instante em que o runtime confirmou o job ausente. */
383
+ function lerSessoesEncerradas(raiz) {
384
+ try {
385
+ const d = JSON.parse(fs.readFileSync(caminhoDeEncerradas(raiz), 'utf8'));
386
+ if (d.versao === 1 && d.sessoes && typeof d.sessoes === 'object') {
387
+ return new Map(Object.entries(d.sessoes).filter((e) => typeof e[1] === 'string'));
388
+ }
389
+ }
390
+ catch { /* sem cache: le a tela, como sempre */ }
391
+ return new Map();
392
+ }
393
+ function gravarSessoesEncerradas(raiz, mapa) {
394
+ try {
395
+ const arquivo = caminhoDeEncerradas(raiz);
396
+ fs.mkdirSync(path.dirname(arquivo), { recursive: true });
397
+ const tmp = `${arquivo}.${process.pid}.tmp`;
398
+ fs.writeFileSync(tmp, JSON.stringify({ versao: 1, sessoes: Object.fromEntries(mapa) }) + '\n', { mode: 0o600 });
399
+ fs.renameSync(tmp, arquivo);
400
+ }
401
+ catch { /* o cache e economia: falhar aqui nunca derruba o radar */ }
402
+ }
403
+ /**
404
+ * Varre o runtime e classifica todas as sessoes.
405
+ *
406
+ * Custo: uma chamada a `claude agents --json --all`, mais uma chamada a `claude logs`
407
+ * POR SESSAO PARADA (e so por essas). Na maquina do incidente, 43 sessoes e 8 paradas.
408
+ */
409
+ function varrerSessoes(opcoes = {}) {
410
+ const quando = opcoes.agora ?? (0, util_1.agora)();
411
+ const atencaoMin = opcoes.atencaoMin ?? exports.ATENCAO_PADRAO_MIN;
412
+ const raiz = opcoes.raiz ?? '';
413
+ const semLogs = opcoes.semLogs === true;
414
+ const linhasLogs = opcoes.linhasLogs ?? 20;
415
+ if (!Number.isInteger(linhasLogs) || linhasLogs < 1 || linhasLogs > 200)
416
+ throw new Error('linhasLogs deve estar entre 1 e 200');
417
+ const registradas = sessoesRegistradas(raiz);
418
+ const nativas = new Map();
419
+ const detalhesNativos = new Map();
420
+ const fimNativas = performance.now() + 1500;
421
+ for (const [sid, reg] of registradas) {
422
+ if (reg.runtime !== 'codex' || !reg.controlador)
423
+ continue;
424
+ let estado = 'unknown', cwd = '';
425
+ try {
426
+ const restante = Math.max(0, fimNativas - performance.now());
427
+ const r = (0, hitl_sessions_1.controleNativo)('codex', raiz, reg.id, sid).consultar(restante);
428
+ const limitacao = r.sessoes[0]?.limitacao;
429
+ if (limitacao)
430
+ detalhesNativos.set(sid, `${limitacao.motivo}: ${limitacao.codigo}; pedido não respondível; interrupção nativa solicitada`);
431
+ else if (!r.ok)
432
+ detalhesNativos.set(sid, 'runtime.unavailable: consulta não confirmada no orçamento nativo de 1500 ms; alerta preservado');
433
+ if (r.ok && r.sessoes.length === 1) {
434
+ estado = r.sessoes[0].estado;
435
+ cwd = r.sessoes[0].cwd;
436
+ }
437
+ }
438
+ catch { /* controller incerto continua alerta, nunca vira sessão ausente */ }
439
+ nativas.set(sid, { sessionId: sid, id: sid, kind: 'codex-controller', name: reg.slug, state: estado, cwd, startedAt: Date.parse(reg.desdeEm ?? '') });
440
+ }
441
+ const consultaClaude = opcoes.consulta ?? (adapter.disponivel()
442
+ ? adapter.consultarSessoes(opcoes.soDaRaiz && raiz ? raiz : undefined, true)
443
+ : { ok: false, sessoes: [], detalhe: 'runtime adapter indisponivel nesta maquina (`claude` fora do PATH)' });
444
+ const consulta = { ...consultaClaude, ok: consultaClaude.ok || nativas.size > 0,
445
+ sessoes: [...consultaClaude.sessoes, ...nativas.values()] };
446
+ if (!consulta.ok) {
447
+ return {
448
+ consultadoEm: quando,
449
+ atencaoMin,
450
+ runtimeConsultado: false,
451
+ runtimeDetalhe: consulta.detalhe,
452
+ logsLidos: false,
453
+ raiz,
454
+ sessoes: [],
455
+ resumo: {
456
+ total: 0,
457
+ precisamDeHumano: 0,
458
+ hitl: 0,
459
+ abandonadas: 0,
460
+ falhas: 0,
461
+ trabalhando: 0,
462
+ desconhecidas: 0,
463
+ acimaDoLimite: 0,
464
+ foraDoOrk: 0,
465
+ },
466
+ };
467
+ }
468
+ const brutas = consulta.sessoes;
469
+ const ledgers = new Map();
470
+ const encerradas = raiz ? lerSessoesEncerradas(raiz) : new Map();
471
+ const fimDasTelas = performance.now() + (opcoes.orcamentoLogsMs ?? exports.ORCAMENTO_LOGS_PADRAO_MS);
472
+ let telasAdiadas = 0;
473
+ // O cache so vale para quem ja e historia: sessao sem thread ou de thread fechada. Sessao de
474
+ // thread aberta pode ser retomada, e ela continua sendo relida a cada varredura, como antes.
475
+ const statusDaThread = new Map();
476
+ const threadFechada = (id) => {
477
+ if (!statusDaThread.has(id)) {
478
+ try {
479
+ statusDaThread.set(id, (0, thread_1.lerThread)(raiz, id).status);
480
+ }
481
+ catch {
482
+ statusDaThread.set(id, null);
483
+ }
484
+ }
485
+ return statusDaThread.get(id) === 'fechada';
486
+ };
487
+ const sessoes = brutas.map((s) => {
488
+ const estado = estadoBruto(s);
489
+ const regra = classificarEstado(nativas.has(s.sessionId) && estado === 'completed' ? 'done' : estado);
490
+ const id = s.id ?? (s.sessionId ?? '').slice(0, 8);
491
+ const idadeMin = idadeEmMinutos(s.startedAt ?? NaN, quando);
492
+ // Os logs custam uma chamada por sessao, entao so as paradas pagam esse preco. E e
493
+ // essa leitura que separa a sessao que espera resposta da que morreu esperando.
494
+ let pergunta = '';
495
+ let alternativas = [];
496
+ let tipoDeHitl = null;
497
+ let jobVivo = null;
498
+ let classe = regra.classe;
499
+ let detalhe = regra.detalhe;
500
+ let precisaDeHumano = regra.precisaDeHumano;
501
+ const registro = registradas.get(s.sessionId ?? '');
502
+ let contextoLogs = [];
503
+ if (nativas.has(s.sessionId)) {
504
+ jobVivo = !['unknown', 'unavailable', 'stopped', 'completed', 'failed'].includes(estado);
505
+ detalhe = detalhesNativos.get(s.sessionId) ?? 'estado confirmado pelo controller Codex vinculado ao despacho; sem attach/stop de Claude';
506
+ }
507
+ else if (regra.classe === 'hitl' && !semLogs) {
508
+ const sid = s.sessionId ?? '';
509
+ // A chave inclui o inicio da instancia: a mesma sessao retomada e outra instancia.
510
+ const chaveDoCache = raiz && sid && (!registro || threadFechada(registro.id)) ? `${sid}|${s.startedAt ?? ''}` : '';
511
+ const encerradaEm = chaveDoCache ? encerradas.get(chaveDoCache) : undefined;
512
+ let r = null;
513
+ let sumiu = false;
514
+ if (encerradaEm) {
515
+ jobVivo = false;
516
+ sumiu = true;
517
+ }
518
+ else if (performance.now() > fimDasTelas) {
519
+ // Sem ler a tela, a sessao continua `hitl` com job incerto: o pulse a mantem visivel.
520
+ telasAdiadas++;
521
+ detalhe = 'leitura da tela adiada: o orcamento de logs desta varredura acabou; a proxima continua daqui';
522
+ }
523
+ else {
524
+ r = adapter.logsDaSessao(id, Math.max(80, linhasLogs));
525
+ jobVivo = r.ok && !JOB_SUMIU.test(r.texto);
526
+ sumiu = !r.ok && JOB_SUMIU.test(r.texto);
527
+ const ausente = sumiu || (!r.ok && SOCKET_AUSENTE.test(r.texto));
528
+ if (ausente && chaveDoCache) {
529
+ encerradas.set(chaveDoCache, quando);
530
+ gravarSessoesEncerradas(raiz, encerradas);
531
+ }
532
+ }
533
+ if (jobVivo === false) {
534
+ classe = 'abandonada';
535
+ tipoDeHitl = 'hitl.encerrado';
536
+ detalhe =
537
+ 'estado `blocked` carimbado, mas o job ja saiu do runtime: a sessao morreu ' +
538
+ 'esperando resposta e o trabalho dela ficou pela metade' +
539
+ (encerradaEm ? ' (job ausente ja confirmado numa varredura anterior)' : '');
540
+ if (sumiu && registro && raiz) {
541
+ try {
542
+ let eventos = ledgers.get(registro.id);
543
+ if (!eventos) {
544
+ eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, registro.id));
545
+ ledgers.set(registro.id, eventos);
546
+ }
547
+ if (temSuperacaoConfirmada(s, registro, eventos)) {
548
+ classe = 'interrompida';
549
+ tipoDeHitl = null;
550
+ precisaDeHumano = false;
551
+ detalhe = 'resíduo da mesma instância superada: encerramento confirmado no ledger e job ausente no runtime';
552
+ }
553
+ }
554
+ catch { /* ausência de evidência preserva o alerta */ }
555
+ }
556
+ }
557
+ else if (jobVivo === true && r) {
558
+ const lido = lerPerguntaDaTela(r.texto);
559
+ pergunta = redigirSegredos(lido.pergunta);
560
+ alternativas = lido.alternativas.map(redigirSegredos);
561
+ tipoDeHitl = lido.tipo;
562
+ if (lido.alternativas.length === 0) {
563
+ contextoLogs = contextoSeguro(r.texto, linhasLogs);
564
+ detalhe =
565
+ 'sessao parada com job vivo, mas a ultima tela nao mostra o menu: ' +
566
+ 'da para ver o que ela pede com `ork sessions logs`';
567
+ }
568
+ }
569
+ }
570
+ return {
571
+ id,
572
+ sessionId: s.sessionId ?? '',
573
+ nome: s.name ?? '(sem nome)',
574
+ cwd: s.cwd ?? '',
575
+ kind: s.kind ?? '?',
576
+ estadoBruto: estado,
577
+ classe,
578
+ tipoDeHitl,
579
+ jobVivo,
580
+ precisaDeHumano,
581
+ detalhe,
582
+ recomendacao: precisaDeHumano ? recomendarPara(classe, tipoDeHitl, id) : '',
583
+ desdeEm: Number.isFinite(s.startedAt) ? new Date(s.startedAt).toISOString() : '',
584
+ idadeMin,
585
+ pergunta,
586
+ alternativas,
587
+ contextoLogs,
588
+ thread: registro ? { id: registro.id, fase: registro.fase, slug: registro.slug } : null,
589
+ comandos: {
590
+ logs: nativas.has(s.sessionId) ? '' : `ork sessions logs ${id}`,
591
+ attach: nativas.has(s.sessionId) ? '' : `claude attach ${s.sessionId ?? id}`,
592
+ parar: nativas.has(s.sessionId) ? '' : `ork sessions stop ${id}`,
593
+ },
594
+ acimaDoLimite: idadeMin >= atencaoMin,
595
+ };
596
+ });
597
+ (0, hitl_estado_1.carimbarSessoes)(sessoes, { raiz, quando, registrar: opcoes.registrar, casa: opcoes.casa, escopo: opcoes.escopo, registrarMaquina: opcoes.registrarMaquina });
598
+ for (const s of sessoes)
599
+ s.acimaDoLimite = s.paradaHaMin !== null && s.paradaHaMin !== undefined && s.paradaHaMin >= atencaoMin;
600
+ // Ordem: quem precisa de humano primeiro, e dentro do grupo a sessao mais VELHA no
601
+ // topo. E a ordem em que o humano deve atacar a fila.
602
+ const peso = (s) => s.classe === 'hitl' ? 0 : s.precisaDeHumano ? 1 : 2;
603
+ sessoes.sort((a, b) => {
604
+ if (peso(a) !== peso(b))
605
+ return peso(a) - peso(b);
606
+ if ((b.paradaHaMin ?? -1) !== (a.paradaHaMin ?? -1))
607
+ return (b.paradaHaMin ?? -1) - (a.paradaHaMin ?? -1);
608
+ return a.id.localeCompare(b.id);
609
+ });
610
+ const visiveis = opcoes.soParadas === true ? sessoes.filter((s) => s.precisaDeHumano) : sessoes;
611
+ return {
612
+ consultadoEm: quando,
613
+ atencaoMin,
614
+ runtimeConsultado: true,
615
+ runtimeDetalhe: '',
616
+ logsLidos: !semLogs,
617
+ telasAdiadas,
618
+ raiz,
619
+ sessoes: visiveis,
620
+ resumo: {
621
+ total: sessoes.length,
622
+ precisamDeHumano: sessoes.filter((s) => s.precisaDeHumano).length,
623
+ hitl: sessoes.filter((s) => s.classe === 'hitl').length,
624
+ abandonadas: sessoes.filter((s) => s.classe === 'abandonada').length,
625
+ falhas: sessoes.filter((s) => s.classe === 'falha').length,
626
+ trabalhando: sessoes.filter((s) => s.classe === 'trabalhando').length,
627
+ desconhecidas: sessoes.filter((s) => s.classe === 'desconhecida').length,
628
+ acimaDoLimite: sessoes.filter((s) => s.precisaDeHumano && s.acimaDoLimite).length,
629
+ foraDoOrk: sessoes.filter((s) => s.thread === null).length,
630
+ },
631
+ };
632
+ }
633
+ /** Diretorio curto para a tabela: o `basename` basta para o humano se localizar. */
634
+ function dirCurto(cwd) {
635
+ const partes = cwd.replace(/\/$/, '').split('/');
636
+ return partes.length <= 1 ? cwd : partes.slice(-2).join('/');
637
+ }
638
+ /** Texto de `ork sessions hitl`. */
639
+ function textoDoRadar(r) {
640
+ const L = [];
641
+ L.push(`Radar de sessoes do runtime (${r.resumo.total} sessao(oes) na maquina)`);
642
+ L.push(` precisam de humano: ${r.resumo.precisamDeHumano} (HITL vivo ${r.resumo.hitl}, ` +
643
+ `abandonada ${r.resumo.abandonadas}, falha ${r.resumo.falhas}, ` +
644
+ `estado desconhecido ${r.resumo.desconhecidas}) | ` +
645
+ `trabalhando: ${r.resumo.trabalhando} | fora do ork: ${r.resumo.foraDoOrk}`);
646
+ L.push(` limite de atencao: ${r.atencaoMin} min (${r.resumo.acimaDoLimite} parada(s) acima, marcadas com !)`);
647
+ if (!r.runtimeConsultado) {
648
+ L.push(` [fail] ${r.runtimeDetalhe}: NAO da para saber quem esta parado`);
649
+ return L.join('\n');
650
+ }
651
+ if (!r.logsLidos) {
652
+ L.push(' [warn] --sem-logs: o radar diz QUEM esta parado, mas nao O QUE cada sessao pergunta');
653
+ }
654
+ L.push('');
655
+ if (r.sessoes.length === 0) {
656
+ L.push(' Nenhuma sessao na varredura.');
657
+ return L.join('\n');
658
+ }
659
+ L.push((0, util_1.tabela)(['ID', 'NOME', 'CLASSE', 'ESTADO', 'TIPO', 'DIRETORIO', 'THREAD', 'IDADE'], r.sessoes.map((s) => [
660
+ s.id,
661
+ s.nome,
662
+ s.classe,
663
+ s.estadoBruto || '(vazio)',
664
+ s.tipoDeHitl ?? '-',
665
+ dirCurto(s.cwd),
666
+ s.thread ? `${s.thread.id}/${s.thread.fase}` : 'fora do ork',
667
+ `${(0, horario_1.duracaoRelativa)(s.idadeMin)}${s.precisaDeHumano && s.acimaDoLimite ? ' !' : ''}`,
668
+ ])));
669
+ const travadas = r.sessoes.filter((s) => s.precisaDeHumano);
670
+ if (travadas.length === 0) {
671
+ L.push('');
672
+ L.push(' Nenhuma sessao esperando o humano.');
673
+ return L.join('\n');
674
+ }
675
+ const vivas = travadas.filter((s) => s.classe !== 'abandonada');
676
+ if (vivas.length > 0) {
677
+ L.push('');
678
+ L.push('Esperando o HUMANO agora, com o job VIVO (responder destrava na hora):');
679
+ for (const s of vivas) {
680
+ L.push(` ${s.id} ${s.nome} [${s.classe}${s.tipoDeHitl ? '/' + s.tipoDeHitl : ''}]`);
681
+ L.push(` onde : ${s.cwd}` +
682
+ (s.thread ? ` (thread ${s.thread.id}, fase ${s.thread.fase})` : ' (sessao fora do ork)'));
683
+ L.push(` por que : ${s.detalhe}`);
684
+ if (s.pergunta)
685
+ L.push(` pergunta : ${s.pergunta}`);
686
+ for (const linha of s.contextoLogs ?? [])
687
+ L.push(` log : ${linha}`);
688
+ for (const a of s.alternativas)
689
+ L.push(` opcao : ${a}`);
690
+ L.push(` parada ha: ${s.paradaHaMin == null ? 'desconhecido; registre com --registrar' : (0, horario_1.duracaoRelativa)(s.paradaHaMin) + ' (desde a primeira observação)'}`);
691
+ L.push(` viva ha : ${(0, horario_1.duracaoRelativa)(s.idadeMin)} (idade da sessao)`);
692
+ L.push(` ver : ${s.comandos.logs}`);
693
+ L.push(` responder: ${s.comandos.attach}`);
694
+ L.push(` recomendo: ${s.recomendacao}`);
695
+ }
696
+ }
697
+ const abandonadas = travadas.filter((s) => s.classe === 'abandonada');
698
+ if (abandonadas.length > 0) {
699
+ L.push('');
700
+ L.push('MORRERAM esperando resposta (job ja saiu: nao adianta responder, o trabalho parou):');
701
+ for (const s of abandonadas) {
702
+ L.push(` ${s.id} ${s.nome}`);
703
+ L.push(` onde : ${s.cwd}` +
704
+ (s.thread ? ` (thread ${s.thread.id}, fase ${s.thread.fase})` : ' (sessao fora do ork)'));
705
+ L.push(` por que : ${s.detalhe}`);
706
+ L.push(` viva ha : ${(0, horario_1.duracaoRelativa)(s.idadeMin)} desde que a sessao comecou`);
707
+ L.push(` recomendo: ${s.recomendacao}`);
708
+ }
709
+ }
710
+ return L.join('\n');
711
+ }