@orkastery/cli 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/check.md +4 -3
- package/adapters/claude-code/commands/goal.md +3 -2
- package/adapters/claude-code/commands/master.md +3 -2
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-guard.js +1 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +161 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +24 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +136 -27
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +460 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +12 -9
- package/dist/auditrun.js +5 -4
- package/dist/board.js +174 -23
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +223 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +184 -25
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +22 -1
- package/dist/gates.js +59 -11
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1741 -94
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +109 -2
- package/dist/licoes.js +221 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +612 -151
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/policies.js +72 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +640 -118
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +700 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +168 -19
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +5 -1
- package/eval/casos/master-metrics.json +1 -1
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +37 -3
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +3 -3
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +28 -7
- package/references/definition-of-done.md +2 -2
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/README.md +3 -3
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/decision-triage/SKILL.md +4 -4
- package/skills/governance/narrative-guardian/SKILL.md +1 -1
- package/skills/governance/roadmap-keeper/SKILL.md +1 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
- package/skills/observability/thread-tracing/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +2 -2
- package/skills/phases/goal-definition/SKILL.md +1 -1
- package/skills/phases/master-metrics/SKILL.md +9 -7
- package/skills/phases/plan-specification/SKILL.md +1 -1
- package/skills/phases/ship-release/SKILL.md +1 -1
package/dist/thread.js
CHANGED
|
@@ -48,10 +48,13 @@ exports.gravarThread = gravarThread;
|
|
|
48
48
|
exports.idDisponivel = idDisponivel;
|
|
49
49
|
exports.criarWorktree = criarWorktree;
|
|
50
50
|
exports.novaThread = novaThread;
|
|
51
|
+
exports.exigirVarianteCompativel = exigirVarianteCompativel;
|
|
51
52
|
exports.blocoDaThread = blocoDaThread;
|
|
52
53
|
exports.pausaNaThread = pausaNaThread;
|
|
53
54
|
exports.pausasDaThread = pausasDaThread;
|
|
55
|
+
exports.canalDaSessao = canalDaSessao;
|
|
54
56
|
exports.resumoDaThread = resumoDaThread;
|
|
57
|
+
exports.threadsDaListagem = threadsDaListagem;
|
|
55
58
|
exports.tabelaDeThreads = tabelaDeThreads;
|
|
56
59
|
exports.exigirFase = exigirFase;
|
|
57
60
|
const fs = __importStar(require("node:fs"));
|
|
@@ -60,15 +63,21 @@ const ciclos_1 = require("./ciclos");
|
|
|
60
63
|
const modos_1 = require("./modos");
|
|
61
64
|
const manifest_1 = require("./manifest");
|
|
62
65
|
const slug_1 = require("./slug");
|
|
66
|
+
const types_1 = require("./types");
|
|
63
67
|
const util_1 = require("./util");
|
|
64
68
|
const ledger_1 = require("./ledger");
|
|
69
|
+
const estado_thread_1 = require("./estado-thread");
|
|
70
|
+
const creation_operation_store_1 = require("./creation-operation-store");
|
|
71
|
+
const horario_1 = require("./horario");
|
|
72
|
+
const maquina_1 = require("./maquina");
|
|
73
|
+
const conducao_texto_1 = require("./conducao-texto");
|
|
65
74
|
/** Diretorio de threads do projeto. */
|
|
66
75
|
function dirThreads(raiz) {
|
|
67
|
-
return path.join((0, manifest_1.dirEstado)(raiz), 'threads');
|
|
76
|
+
return path.join((0, manifest_1.dirEstado)((0, estado_thread_1.raizDoEstado)(raiz)), 'threads');
|
|
68
77
|
}
|
|
69
78
|
/** Diretorio de uma thread. */
|
|
70
79
|
function dirThread(raiz, id) {
|
|
71
|
-
return
|
|
80
|
+
return (0, estado_thread_1.estadoCanonico)(raiz, id);
|
|
72
81
|
}
|
|
73
82
|
/** Caminho do `thread.json`. */
|
|
74
83
|
function caminhoThread(raiz, id) {
|
|
@@ -96,7 +105,7 @@ function lerThread(raiz, id) {
|
|
|
96
105
|
/** Grava a thread, sempre atualizando o carimbo de alteracao. */
|
|
97
106
|
function gravarThread(raiz, thread) {
|
|
98
107
|
thread.atualizadaEm = (0, util_1.agora)();
|
|
99
|
-
(0,
|
|
108
|
+
(0, creation_operation_store_1.writeCreationJson)(caminhoThread(raiz, thread.id), thread);
|
|
100
109
|
}
|
|
101
110
|
/** Carimba a base da thread: branch e commit reais no momento da criacao. */
|
|
102
111
|
function carimbarBase(raiz) {
|
|
@@ -148,10 +157,33 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
|
148
157
|
if (!criada) {
|
|
149
158
|
throw new Error(`worktree nao apareceu em \`git worktree list\` apos a criacao: ${dir}`);
|
|
150
159
|
}
|
|
160
|
+
(0, estado_thread_1.vincularEstado)(raiz, id, dir);
|
|
151
161
|
return { dir, branch };
|
|
152
162
|
}
|
|
153
163
|
/** Cria a thread: valida modo contra o manifesto, gera o slug e carimba a base. */
|
|
154
164
|
function novaThread(carregado, opcoes) {
|
|
165
|
+
if (opcoes.dryRun)
|
|
166
|
+
return criarThreadSerializada(carregado, opcoes);
|
|
167
|
+
return (0, creation_operation_store_1.withCreationLock)((0, estado_thread_1.raizDoEstado)(carregado.raiz), 'threads', () => criarThreadSerializada(carregado, opcoes));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* I-42 (D12): variante que redesenha blocos nao cabe num modo de bloco unico e parcial.
|
|
171
|
+
*
|
|
172
|
+
* Regra por desenho, nao por nome: `#Fast` tem um bloco so, com uma fase, e `gap` sobre ele
|
|
173
|
+
* apagaria o ciclo inteiro, enquanto `goal-plan` seria um no-op silencioso. A recusa sai antes
|
|
174
|
+
* de aplicar a variante, com o par variante-modo na mensagem. O `#Auto`, que tambem tem um bloco
|
|
175
|
+
* so mas percorre as seis fases, continua aceitando as duas, porque nelas ha o que redesenhar.
|
|
176
|
+
*/
|
|
177
|
+
function exigirVarianteCompativel(variante, def) {
|
|
178
|
+
if (!(0, ciclos_1.definicaoDaVariante)(variante).mudaBlocos)
|
|
179
|
+
return;
|
|
180
|
+
const [unico, ...resto] = def.blocos;
|
|
181
|
+
if (unico && resto.length === 0 && unico.fases.length < types_1.FASES.length) {
|
|
182
|
+
throw new Error(`o ciclo ${variante} redesenha os blocos e o modo ${def.tag} tem um bloco so ` +
|
|
183
|
+
`(${unico.fases.join('-')}): crie a thread sem --ciclo ou escolha um modo de ciclo completo`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function criarThreadSerializada(carregado, opcoes) {
|
|
155
187
|
const { raiz, manifesto } = carregado;
|
|
156
188
|
const permitidos = manifesto.conduction.allowed_modes;
|
|
157
189
|
if (!permitidos.includes(opcoes.modo)) {
|
|
@@ -167,6 +199,8 @@ function novaThread(carregado, opcoes) {
|
|
|
167
199
|
const variante = opcoes.variante ?? null;
|
|
168
200
|
// A variante muda o desenho dos blocos ANTES do slug: a parte 3 do slug vem do
|
|
169
201
|
// primeiro bloco, entao um ciclo `goal-plan` ja nasce com o slug `f12`.
|
|
202
|
+
if (variante)
|
|
203
|
+
exigirVarianteCompativel(variante, def);
|
|
170
204
|
const blocos = (0, ciclos_1.aplicarVariante)(def.blocos, variante);
|
|
171
205
|
if (variante) {
|
|
172
206
|
const dv = (0, ciclos_1.definicaoDaVariante)(variante);
|
|
@@ -183,15 +217,42 @@ function novaThread(carregado, opcoes) {
|
|
|
183
217
|
else if (opcoes.branch) {
|
|
184
218
|
throw new Error('--branch so vale com --ciclo merge-branch');
|
|
185
219
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
const reservation = opcoes.reservation;
|
|
221
|
+
const operation = reservation ? (0, creation_operation_store_1.readCreationOperation)(raiz, reservation.operationId, reservation.principal) : null;
|
|
222
|
+
if (operation && (!operation.reserved.threadIds.includes(reservation.threadId)
|
|
223
|
+
|| !reservation.threadId.startsWith(abbrev + '-') || !operation.reserved.ticketId
|
|
224
|
+
|| operation.request.mode !== opcoes.modo || ['compensating', 'compensated', 'compensation_failed'].includes(operation.state)
|
|
225
|
+
|| opcoes.criarWorktree || opcoes.worktree || opcoes.variante || opcoes.slug || opcoes.dryRun)) {
|
|
226
|
+
throw new Error('creation.conflict: reserva de thread incompatível');
|
|
227
|
+
}
|
|
228
|
+
const origin = operation ? { operationId: operation.operationId, ticketId: operation.reserved.ticketId, requestHash: operation.requestHash } : undefined;
|
|
229
|
+
if (reservation && fs.existsSync(caminhoThread(raiz, reservation.threadId))) {
|
|
230
|
+
const existing = lerThread(raiz, reservation.threadId);
|
|
231
|
+
if (existing.creationOrigin?.operationId !== origin.operationId || existing.creationOrigin?.ticketId !== origin.ticketId
|
|
232
|
+
|| existing.creationOrigin?.requestHash !== origin.requestHash || existing.modo !== opcoes.modo || existing.nome !== opcoes.nome) {
|
|
233
|
+
throw new Error('creation.conflict: thread reservada pertence a outra intenção');
|
|
234
|
+
}
|
|
235
|
+
if (!(0, ledger_1.lerLedger)(dirThread(raiz, existing.id)).some((event) => event.tipo === ledger_1.TIPOS_DE_EVENTO.threadCriada)) {
|
|
236
|
+
(0, ledger_1.registrar)(dirThread(raiz, existing.id), existing.id, ledger_1.TIPOS_DE_EVENTO.threadCriada, { creationOrigin: origin, recovered: true });
|
|
237
|
+
}
|
|
238
|
+
return { thread: existing, gravada: false };
|
|
239
|
+
}
|
|
240
|
+
if (reservation && fs.existsSync(dirThread(raiz, reservation.threadId))
|
|
241
|
+
&& fs.readdirSync(dirThread(raiz, reservation.threadId)).some((name) => !/^\.thread\.json\..+\.tmp$/.test(name))) {
|
|
242
|
+
throw new Error('creation.conflict: diretório reservado sem origem confirmada');
|
|
243
|
+
}
|
|
244
|
+
const { id, assunto } = reservation
|
|
245
|
+
? { id: reservation.threadId, assunto: reservation.threadId.slice(abbrev.length + 1) }
|
|
246
|
+
: opcoes.dryRun
|
|
247
|
+
? { id: `${abbrev}-${assuntoBruto}`, assunto: assuntoBruto }
|
|
248
|
+
: idDisponivel(raiz, abbrev, assuntoBruto);
|
|
189
249
|
const slug = opcoes.slug ?? (0, slug_1.montarSlug)(abbrev, assunto, blocos[0].slugFases);
|
|
190
250
|
// O override `--slug` do builder tambem passa pela regex canonica.
|
|
191
251
|
if (!(0, slug_1.slugValido)(slug)) {
|
|
192
252
|
throw new Error(`slug invalido: "${slug}" (regex canonica: ${slug_1.REGEX_SLUG.source})`);
|
|
193
253
|
}
|
|
194
254
|
const thread = {
|
|
255
|
+
...(origin ? { creationOrigin: origin } : {}),
|
|
195
256
|
id,
|
|
196
257
|
slug,
|
|
197
258
|
nome: opcoes.nome,
|
|
@@ -204,9 +265,13 @@ function novaThread(carregado, opcoes) {
|
|
|
204
265
|
criadaEm: (0, util_1.agora)(),
|
|
205
266
|
atualizadaEm: (0, util_1.agora)(),
|
|
206
267
|
projeto: { name: manifesto.project.name, abbrev },
|
|
268
|
+
maquina: (0, maquina_1.nomeDaMaquina)(),
|
|
269
|
+
...(opcoes.roadmap ? { roadmap: opcoes.roadmap } : {}),
|
|
207
270
|
base: carimbarBase(raiz),
|
|
208
271
|
worktree: opcoes.worktree ?? null,
|
|
209
272
|
sessoes: [],
|
|
273
|
+
...(opcoes.exigeRuntimeDiferente === true ? { exigeRuntimeDiferente: true } : {}),
|
|
274
|
+
...(opcoes.doneWhen && opcoes.doneWhen.length > 0 ? { doneWhen: opcoes.doneWhen } : {}),
|
|
210
275
|
decisoes: [],
|
|
211
276
|
claims: [],
|
|
212
277
|
leases: [],
|
|
@@ -235,6 +300,7 @@ function novaThread(carregado, opcoes) {
|
|
|
235
300
|
}
|
|
236
301
|
gravarThread(raiz, thread);
|
|
237
302
|
(0, ledger_1.registrar)(dirThread(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.threadCriada, {
|
|
303
|
+
...(origin ? { creationOrigin: origin } : {}),
|
|
238
304
|
slug,
|
|
239
305
|
modo: opcoes.modo,
|
|
240
306
|
tag: def.tag,
|
|
@@ -245,6 +311,8 @@ function novaThread(carregado, opcoes) {
|
|
|
245
311
|
worktree: thread.worktree,
|
|
246
312
|
variante,
|
|
247
313
|
branchDeOrigem: opcoes.branch ?? null,
|
|
314
|
+
maquina: thread.maquina,
|
|
315
|
+
...(thread.roadmap ? { roadmap: thread.roadmap } : {}),
|
|
248
316
|
});
|
|
249
317
|
if (worktreeCriada) {
|
|
250
318
|
(0, ledger_1.registrar)(dirThread(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.worktreeCriada, {
|
|
@@ -280,48 +348,94 @@ function pausasDaThread(thread) {
|
|
|
280
348
|
return thread.blocos.filter((b) => b.pausa).length;
|
|
281
349
|
}
|
|
282
350
|
/** Texto do resumo de uma thread recem-criada ou consultada. */
|
|
283
|
-
|
|
351
|
+
/** I-36 (T10): o canal de origem de uma sessao; a de antes do campo le como `desconhecido`, nunca `cli`. */
|
|
352
|
+
function canalDaSessao(s) {
|
|
353
|
+
return s.canal ?? 'desconhecido';
|
|
354
|
+
}
|
|
355
|
+
function resumoDaThread(thread, conducao) {
|
|
284
356
|
const def = (0, modos_1.definicaoDoModo)(thread.modo);
|
|
285
357
|
const linhas = [];
|
|
286
358
|
linhas.push(` id ${thread.id}`);
|
|
287
359
|
linhas.push(` slug ${thread.slug}`);
|
|
288
360
|
linhas.push(` nome ${thread.nome}`);
|
|
289
|
-
linhas.push(` modo ${def.tag} (${pausasDaThread(thread)} pausas humanas)`);
|
|
361
|
+
linhas.push(` modo ${def.tag} (${pausasDaThread(thread)} pausas humanas previstas)`);
|
|
290
362
|
linhas.push(` ciclo ${thread.variante ?? 'padrao do modo'}`);
|
|
291
363
|
linhas.push(` fase ${thread.faseAtual}`);
|
|
292
364
|
linhas.push(` status ${thread.status}`);
|
|
293
365
|
linhas.push(` base ${thread.base.branch} @ ${thread.base.commit.slice(0, 8)}`);
|
|
294
366
|
linhas.push(` worktree ${thread.worktree ?? '(raiz do projeto)'}`);
|
|
367
|
+
// I-36 (T17): quem passa a leitura da conducao ve a linha unica; sem ela o resumo fica como era.
|
|
368
|
+
if (conducao !== undefined)
|
|
369
|
+
linhas.push(` conducao ${conducao ? (0, conducao_texto_1.linhaDeConducao)(conducao) : 'ninguem conduz agora'}`);
|
|
370
|
+
// I-43 (D4, viga a): a exigencia so aparece quando existe, para nao poluir o resumo
|
|
371
|
+
// das threads que nao a declararam.
|
|
372
|
+
if (thread.exigeRuntimeDiferente === true) {
|
|
373
|
+
linhas.push(' validacao CHECK em runtime diferente do GO (exigido pela thread)');
|
|
374
|
+
}
|
|
375
|
+
// I-43 (D4, viga b): o criterio aparece COM o comando. Mostrar so o criterio seria
|
|
376
|
+
// reproduzir a prosa do envelope numa tela nova.
|
|
377
|
+
if (thread.doneWhen && thread.doneWhen.length > 0) {
|
|
378
|
+
linhas.push(` pronto quando (${thread.doneWhen.length} criterio(s) executaveis)`);
|
|
379
|
+
for (const d of thread.doneWhen)
|
|
380
|
+
linhas.push(` ${d.criterio}\n $ ${d.comando}`);
|
|
381
|
+
}
|
|
295
382
|
if (thread.score) {
|
|
296
383
|
linhas.push(` score ${thread.score.valor}/5 por ${thread.score.avaliadoPor} ` +
|
|
297
|
-
`(${thread.score.regime}) em ${thread.score.avaliadoEm}`);
|
|
384
|
+
`(${thread.score.regime}) em ${(0, horario_1.formatarDataHoraRotulada)(thread.score.avaliadoEm)}`);
|
|
298
385
|
}
|
|
299
|
-
linhas.push(' blocos');
|
|
386
|
+
linhas.push(' blocos previstos');
|
|
300
387
|
for (const b of thread.blocos) {
|
|
301
|
-
const marca = b.pausa ? `pausa: ${b.pausaSobre}` : 'sem pausa
|
|
388
|
+
const marca = b.pausa ? `pausa prevista: ${b.pausaSobre}` : 'sem pausa humana prevista';
|
|
302
389
|
linhas.push(` ${b.slugFases.padEnd(6)} ${b.fases.join('-').padEnd(18)} ${marca}`);
|
|
303
390
|
}
|
|
304
391
|
return linhas.join('\n');
|
|
305
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* As threads de `ork thread list`, ja filtradas.
|
|
395
|
+
*
|
|
396
|
+
* I-43 (D8): o DEFAULT mostra o que NAO esta fechado, e nao "so as abertas".
|
|
397
|
+
* O GOAL pediu "so abertas", e a medida corrigiu o pedido: existe thread `pausada`
|
|
398
|
+
* em disco, e um default que a esconde esconde justamente a que mais pede atencao.
|
|
399
|
+
*
|
|
400
|
+
* O custo medido era o default: 137 linhas para as 31 que interessavam, e 71 delas
|
|
401
|
+
* (52 por cento) eram `Sessao adotada claude-bg <uuid>`, lixo do mecanismo de adocao.
|
|
402
|
+
* Quem pagava era quem nao sabia que existia flag.
|
|
403
|
+
*/
|
|
404
|
+
function threadsDaListagem(raiz, opcoes = {}) {
|
|
405
|
+
return listarIds(raiz)
|
|
406
|
+
.map((id) => lerThread(raiz, id))
|
|
407
|
+
.filter((t) => opcoes.todas === true || t.status !== 'fechada')
|
|
408
|
+
.map((t) => ({
|
|
409
|
+
id: t.id,
|
|
410
|
+
slug: t.slug,
|
|
411
|
+
modo: String(t.modo),
|
|
412
|
+
tag: (0, modos_1.tagDoModo)(t.modo),
|
|
413
|
+
fase: t.faseAtual,
|
|
414
|
+
status: t.status,
|
|
415
|
+
pausas: pausasDaThread(t),
|
|
416
|
+
sessoes: t.sessoes.length,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
306
419
|
/** Tabela de `ork thread list`. */
|
|
307
|
-
function tabelaDeThreads(raiz) {
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
310
|
-
|
|
420
|
+
function tabelaDeThreads(raiz, opcoes = {}) {
|
|
421
|
+
const linhas = threadsDaListagem(raiz, opcoes);
|
|
422
|
+
if (linhas.length === 0) {
|
|
423
|
+
// A divida paga aqui: a mensagem sugeria `--modo default`, um modo que
|
|
424
|
+
// `parseModo` recusa, e e a origem provavel das 3 threads com `modo: default`
|
|
425
|
+
// gravadas em disco. A sugestao agora e um modo que existe.
|
|
426
|
+
const vazio = opcoes.todas === true
|
|
427
|
+
? 'Nenhuma thread em .orkastery/threads.'
|
|
428
|
+
: 'Nenhuma thread aberta ou pausada (use --todas para ver as fechadas).';
|
|
429
|
+
return `${vazio} Crie uma com: ork thread new "<nome>" --modo ${modos_1.ORDEM_DOS_MODOS[0]}`;
|
|
311
430
|
}
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
String(pausasDaThread(t)),
|
|
321
|
-
String(t.sessoes.length),
|
|
322
|
-
];
|
|
323
|
-
});
|
|
324
|
-
return (0, util_1.tabela)(['ID', 'SLUG', 'MODO', 'FASE', 'STATUS', 'PAUSAS', 'SESSOES'], linhas);
|
|
431
|
+
const corpo = linhas.map((l) => [
|
|
432
|
+
l.id, l.slug, l.tag, l.fase, l.status, String(l.pausas), String(l.sessoes),
|
|
433
|
+
]);
|
|
434
|
+
const t = (0, util_1.tabela)(['ID', 'SLUG', 'MODO', 'FASE', 'STATUS', 'PAUSAS', 'SESSOES'], corpo);
|
|
435
|
+
if (opcoes.todas === true)
|
|
436
|
+
return t;
|
|
437
|
+
const fechadas = listarIds(raiz).length - linhas.length;
|
|
438
|
+
return fechadas > 0 ? `${t}\n\n ${fechadas} fechada(s) omitida(s). Use --todas para ver todas.` : t;
|
|
325
439
|
}
|
|
326
440
|
/** Fase valida e pertencente ao ciclo da thread. */
|
|
327
441
|
function exigirFase(thread, bruto) {
|
package/dist/tokens.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* `ork gate next`: o gate de tokens (
|
|
3
|
+
* `ork gate next`: o gate de tokens (guia: docs/guias/memoria-e-handoff.md).
|
|
4
4
|
*
|
|
5
5
|
* Ao fim de cada fase, ANTES de despachar o proximo passo, o `ork` mede a lotacao da
|
|
6
6
|
* janela da sessao atual e decide entre continuar na MESMA sessao ou abrir uma NOVA.
|
package/dist/util.js
CHANGED
|
@@ -50,18 +50,20 @@ const node_child_process_1 = require("node:child_process");
|
|
|
50
50
|
const fs = __importStar(require("node:fs"));
|
|
51
51
|
const path = __importStar(require("node:path"));
|
|
52
52
|
/** Executa um comando capturando saida, sem shell (argumentos vao como array). */
|
|
53
|
-
function exec(cmd, args, cwd, timeoutMs = 120000) {
|
|
53
|
+
function exec(cmd, args, cwd, timeoutMs = 120000, env = process.env) {
|
|
54
54
|
const r = (0, node_child_process_1.spawnSync)(cmd, args, {
|
|
55
55
|
cwd,
|
|
56
56
|
encoding: 'utf8',
|
|
57
57
|
timeout: timeoutMs,
|
|
58
|
-
env
|
|
58
|
+
env,
|
|
59
59
|
});
|
|
60
60
|
return {
|
|
61
61
|
ok: r.status === 0,
|
|
62
62
|
code: r.status ?? -1,
|
|
63
63
|
stdout: (r.stdout ?? '').toString(),
|
|
64
64
|
stderr: (r.stderr ?? '').toString(),
|
|
65
|
+
signal: r.signal ?? null,
|
|
66
|
+
...(r.error ? { error: r.error.code ?? r.error.message } : {}),
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
/** O binario esta no PATH? */
|
|
@@ -0,0 +1,265 @@
|
|
|
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.criarExecutorSandbox = criarExecutorSandbox;
|
|
37
|
+
/** VERIFY interno sob sandbox Codex local, sem modelo e sem fallback não confinado.
|
|
38
|
+
* Contrato de escrita/rede, não de confidencialidade de leitura do filesystem.
|
|
39
|
+
* API síncrona: timeout rígido; não promete cancelamento reativo por AbortSignal.
|
|
40
|
+
*/
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const node_url_1 = require("node:url");
|
|
46
|
+
const verify_1 = require("./verify");
|
|
47
|
+
const MAX_OUTPUT = 1024 * 1024;
|
|
48
|
+
/** Supervisor Linux fixo: subreaper recolhe também daemons/setsid.
|
|
49
|
+
* Nunca executa código de claim fora do sandbox. Python é infraestrutura local, sem pacotes. */
|
|
50
|
+
const SUPERVISOR = String.raw `
|
|
51
|
+
import ctypes, errno, json, os, selectors, signal, subprocess, sys, time
|
|
52
|
+
p = json.load(sys.stdin)
|
|
53
|
+
libc = ctypes.CDLL(None, use_errno=True)
|
|
54
|
+
if libc.prctl(36, 1, 0, 0, 0) != 0:
|
|
55
|
+
raise OSError(ctypes.get_errno(), 'PR_SET_CHILD_SUBREAPER falhou')
|
|
56
|
+
state = {'failure': None}
|
|
57
|
+
def interrupted(_sig, _frame): state['failure'] = 'supervisor interrompido'
|
|
58
|
+
signal.signal(signal.SIGTERM, interrupted)
|
|
59
|
+
def cleanup():
|
|
60
|
+
deadline = time.monotonic() + 3
|
|
61
|
+
while True:
|
|
62
|
+
with open('/proc/self/task/' + str(os.getpid()) + '/children') as f:
|
|
63
|
+
children = [int(x) for x in f.read().split()]
|
|
64
|
+
# Filhos vivos ou zombies ainda não reaped: seus PIDs não podem ser reutilizados.
|
|
65
|
+
for pid in children:
|
|
66
|
+
try: os.kill(pid, signal.SIGKILL)
|
|
67
|
+
except ProcessLookupError: pass
|
|
68
|
+
try:
|
|
69
|
+
while True:
|
|
70
|
+
pid, _status = os.waitpid(-1, os.WNOHANG)
|
|
71
|
+
if pid == 0: break
|
|
72
|
+
except ChildProcessError: return True
|
|
73
|
+
if time.monotonic() >= deadline: return False
|
|
74
|
+
time.sleep(.005)
|
|
75
|
+
stdout = bytearray(); stderr = bytearray(); code = -1
|
|
76
|
+
try:
|
|
77
|
+
child = subprocess.Popen([p['bin'], *p['args']], cwd=p['cwd'], env=p['env'],
|
|
78
|
+
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True)
|
|
79
|
+
selector = selectors.DefaultSelector()
|
|
80
|
+
selector.register(child.stdout, selectors.EVENT_READ, stdout)
|
|
81
|
+
selector.register(child.stderr, selectors.EVENT_READ, stderr)
|
|
82
|
+
deadline = time.monotonic() + p['timeoutMs']/1000
|
|
83
|
+
while True:
|
|
84
|
+
if state['failure']: break
|
|
85
|
+
if time.monotonic() >= deadline:
|
|
86
|
+
state['failure'] = 'timeout do sandbox'; break
|
|
87
|
+
if child.poll() is not None and not selector.get_map():
|
|
88
|
+
code = child.returncode; break
|
|
89
|
+
for key, _ in selector.select(.025):
|
|
90
|
+
data = os.read(key.fileobj.fileno(), 65536)
|
|
91
|
+
if not data: selector.unregister(key.fileobj); continue
|
|
92
|
+
if len(stdout)+len(stderr)+len(data) > p['maxOutput']:
|
|
93
|
+
state['failure'] = 'limite de saída excedido'; break
|
|
94
|
+
key.data.extend(data)
|
|
95
|
+
except Exception as e: state['failure'] = 'sandbox indisponível: ' + str(e)
|
|
96
|
+
finally:
|
|
97
|
+
cleanup_complete = cleanup()
|
|
98
|
+
if not cleanup_complete: state['failure'] = 'descendente do sandbox não encerrou no prazo'
|
|
99
|
+
print(json.dumps({'code': -1 if state['failure'] else code,
|
|
100
|
+
'stdout': stdout.decode(errors='replace'), 'stderr': stderr.decode(errors='replace'), 'failure': state['failure'], 'cleanupComplete': cleanup_complete}))
|
|
101
|
+
`;
|
|
102
|
+
/** Somente WT e leaf scratch privado são graváveis; configuração e parent continuam readonly. */
|
|
103
|
+
function estadoSandbox(worktree, scratch) {
|
|
104
|
+
const entries = [
|
|
105
|
+
{ path: { type: 'special', value: { kind: 'root' } }, access: 'read' },
|
|
106
|
+
{ path: { type: 'path', path: worktree }, access: 'write' },
|
|
107
|
+
{ path: { type: 'path', path: scratch }, access: 'write' },
|
|
108
|
+
];
|
|
109
|
+
// Metadados/configuração de host e estado canônico não pertencem ao comando testado.
|
|
110
|
+
for (const nome of ['.git', '.orkastery', '.codex', '.claude', '.agents']) {
|
|
111
|
+
const alvo = path.join(worktree, nome);
|
|
112
|
+
entries.push({ path: { type: 'path', path: alvo }, access: 'read' });
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
permissionProfile: { type: 'managed', file_system: { type: 'restricted', entries }, network: 'restricted' },
|
|
116
|
+
sandboxCwd: (0, node_url_1.pathToFileURL)(worktree).href,
|
|
117
|
+
useLegacyLandlock: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function criarExecutorSandbox(opcoes) {
|
|
121
|
+
if (process.platform !== 'linux')
|
|
122
|
+
throw new Error('executor VERIFY sandbox disponível somente em Linux');
|
|
123
|
+
if (!path.isAbsolute(opcoes.worktree))
|
|
124
|
+
throw new Error('worktree deve ser absoluta');
|
|
125
|
+
const worktree = fs.realpathSync(opcoes.worktree);
|
|
126
|
+
if (!fs.statSync(worktree).isDirectory() || worktree === path.parse(worktree).root)
|
|
127
|
+
throw new Error('worktree inválida');
|
|
128
|
+
const original = fs.statSync(worktree);
|
|
129
|
+
const timeoutMs = opcoes.timeoutMs ?? 60_000;
|
|
130
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > verify_1.TIMEOUT_VERIFY_MS)
|
|
131
|
+
throw new Error('timeout sandbox fora do limite');
|
|
132
|
+
// Binário local aprovado da instalação; não procuramos executável fornecido pela claim.
|
|
133
|
+
const bin = fs.realpathSync('/usr/bin/codex');
|
|
134
|
+
const binStat = fs.statSync(bin);
|
|
135
|
+
const isolamento = fs.mkdtempSync(path.join(os.tmpdir(), 'ork-verify-config-'));
|
|
136
|
+
if (fs.realpathSync(isolamento) === worktree || fs.realpathSync(isolamento).startsWith(worktree + path.sep)) {
|
|
137
|
+
fs.rmdirSync(isolamento);
|
|
138
|
+
throw Error('scratch VERIFY deve ficar fora da worktree');
|
|
139
|
+
}
|
|
140
|
+
const isolamentoStat = fs.lstatSync(isolamento);
|
|
141
|
+
const identidadeIgual = (file, original) => {
|
|
142
|
+
const st = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
143
|
+
return !!st && st.isDirectory() && !st.isSymbolicLink() && st.dev === original.dev && st.ino === original.ino && st.uid === original.uid;
|
|
144
|
+
};
|
|
145
|
+
const codexHome = path.join(isolamento, 'codex');
|
|
146
|
+
fs.mkdirSync(codexHome);
|
|
147
|
+
fs.writeFileSync(path.join(codexHome, 'config.toml'), '');
|
|
148
|
+
let fechado = false, consumidoresEncerrados = true, erroCleanup = '';
|
|
149
|
+
const fechar = () => {
|
|
150
|
+
if (fechado) {
|
|
151
|
+
if (erroCleanup)
|
|
152
|
+
throw Error(erroCleanup);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (erroCleanup)
|
|
156
|
+
throw Error(erroCleanup);
|
|
157
|
+
if (!consumidoresEncerrados)
|
|
158
|
+
throw Error('cleanup incompleto: consumidores não comprovadamente encerrados; isolamento preservado: ' + isolamento);
|
|
159
|
+
if (!identidadeIgual(isolamento, isolamentoStat))
|
|
160
|
+
throw Error('cleanup incompleto: isolamento substituído; preservado');
|
|
161
|
+
fechado = true;
|
|
162
|
+
fs.rmSync(isolamento, { recursive: true, force: false });
|
|
163
|
+
};
|
|
164
|
+
const executar = (nome, comando, cwd) => {
|
|
165
|
+
if (fechado)
|
|
166
|
+
throw new Error('executor sandbox fechado');
|
|
167
|
+
if (erroCleanup || !consumidoresEncerrados)
|
|
168
|
+
throw Error(erroCleanup || 'cleanup incompleto: consumidor pendente');
|
|
169
|
+
const atual = fs.statSync(worktree);
|
|
170
|
+
const atualBin = fs.statSync(bin);
|
|
171
|
+
if (fs.realpathSync(cwd) !== worktree || atual.dev !== original.dev || atual.ino !== original.ino ||
|
|
172
|
+
atualBin.dev !== binStat.dev || atualBin.ino !== binStat.ino)
|
|
173
|
+
throw new Error('escopo/binário do executor sandbox mudou');
|
|
174
|
+
if (!identidadeIgual(isolamento, isolamentoStat))
|
|
175
|
+
throw Error('isolamento sandbox mudou');
|
|
176
|
+
const scratch = fs.mkdtempSync(path.join(isolamento, 'scratch-'));
|
|
177
|
+
const scratchStat = fs.lstatSync(scratch);
|
|
178
|
+
const criados = [];
|
|
179
|
+
const home = path.join(scratch, 'home');
|
|
180
|
+
const tmp = path.join(scratch, 'tmp');
|
|
181
|
+
fs.mkdirSync(home);
|
|
182
|
+
fs.mkdirSync(tmp);
|
|
183
|
+
try {
|
|
184
|
+
// Diretórios vazios não aparecem no Git. Não criar arquivos sintéticos nem esconder untracked.
|
|
185
|
+
for (const nome of ['.git', '.orkastery', '.codex', '.claude', '.agents']) {
|
|
186
|
+
const file = path.join(worktree, nome);
|
|
187
|
+
let st = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
188
|
+
if (!st) {
|
|
189
|
+
fs.mkdirSync(file, { mode: 0o700 });
|
|
190
|
+
st = fs.lstatSync(file);
|
|
191
|
+
criados.push({ file, stat: st });
|
|
192
|
+
}
|
|
193
|
+
if (st.isSymbolicLink() || !(st.isDirectory() || (nome === '.git' && st.isFile() && st.nlink === 1)))
|
|
194
|
+
throw Error('metadado protegido inesperado; objeto preservado: ' + nome);
|
|
195
|
+
}
|
|
196
|
+
for (const item of criados)
|
|
197
|
+
if (!identidadeIgual(item.file, item.stat))
|
|
198
|
+
throw Error('metadado temporário mudou antes do lançamento');
|
|
199
|
+
const env = { PATH: '/usr/local/bin:/usr/bin:/bin', HOME: home, CODEX_HOME: codexHome,
|
|
200
|
+
TMPDIR: tmp, TMP: tmp, TEMP: tmp, LANG: 'C.UTF-8', CI: '1',
|
|
201
|
+
XDG_CACHE_HOME: path.join(home, '.cache'), npm_config_cache: path.join(home, '.npm') };
|
|
202
|
+
const args = ['sandbox', '--sandbox-state-json', JSON.stringify(estadoSandbox(worktree, scratch)),
|
|
203
|
+
'--sandbox-state-disable-network', '/bin/bash', '-c', comando];
|
|
204
|
+
consumidoresEncerrados = false;
|
|
205
|
+
const r = (0, node_child_process_1.spawnSync)('/usr/bin/python3', ['-c', SUPERVISOR], {
|
|
206
|
+
input: JSON.stringify({ bin, args, cwd: worktree, env, timeoutMs, maxOutput: MAX_OUTPUT }),
|
|
207
|
+
cwd: isolamento, env, encoding: 'utf8', maxBuffer: 4 * MAX_OUTPUT,
|
|
208
|
+
timeout: timeoutMs + 5_000, killSignal: 'SIGTERM',
|
|
209
|
+
});
|
|
210
|
+
let resultado;
|
|
211
|
+
try {
|
|
212
|
+
if (r.error || r.status !== 0)
|
|
213
|
+
throw new Error(r.error?.message ?? `supervisor saiu ${r.status}`);
|
|
214
|
+
const v = JSON.parse(r.stdout);
|
|
215
|
+
if (!v || typeof v !== 'object')
|
|
216
|
+
throw new Error('resposta inválida');
|
|
217
|
+
const x = v;
|
|
218
|
+
if (!Number.isInteger(x.code) || typeof x.stdout !== 'string' || typeof x.stderr !== 'string' ||
|
|
219
|
+
!(x.failure === null || typeof x.failure === 'string') || typeof x.cleanupComplete !== 'boolean')
|
|
220
|
+
throw new Error('resposta inválida');
|
|
221
|
+
resultado = x;
|
|
222
|
+
consumidoresEncerrados = resultado.cleanupComplete;
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
resultado = { code: -1, stdout: '', stderr: r.stderr ?? '', failure: `falha fechada do sandbox: ${String(e)}`, cleanupComplete: false };
|
|
226
|
+
}
|
|
227
|
+
const stderr = resultado.stderr + (resultado.failure ? '\n' + resultado.failure : '');
|
|
228
|
+
const resumo = (resultado.stdout + '\n' + stderr).trim().split('\n').slice(-10).join('\n').slice(-800);
|
|
229
|
+
return { nome, comando, ok: resultado.code === 0 && !resultado.failure, code: resultado.code,
|
|
230
|
+
resumo, stdout: resultado.stdout, stderr, executor: 'codex-sandbox' };
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
if (!consumidoresEncerrados) {
|
|
234
|
+
erroCleanup = 'cleanup incompleto: consumidores não comprovadamente encerrados; temporários preservados: ' + isolamento;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const erros = [];
|
|
238
|
+
for (const item of criados.reverse()) {
|
|
239
|
+
try {
|
|
240
|
+
if (!identidadeIgual(item.file, item.stat))
|
|
241
|
+
throw Error('identidade mudou');
|
|
242
|
+
// rmdir é a checagem atômica de vazio. Nunca recursive/force nos paths da WT.
|
|
243
|
+
fs.rmdirSync(item.file);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
erros.push(path.basename(item.file));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
if (!identidadeIgual(isolamento, isolamentoStat) || !identidadeIgual(scratch, scratchStat))
|
|
251
|
+
throw Error('scratch mudou');
|
|
252
|
+
fs.rmSync(scratch, { recursive: true, force: false });
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
erros.push('scratch');
|
|
256
|
+
}
|
|
257
|
+
if (erros.length)
|
|
258
|
+
erroCleanup = 'cleanup incompleto: objetos substituídos ou não vazios preservados: ' + erros.join(', ') + '; isolamento preservado: ' + isolamento;
|
|
259
|
+
}
|
|
260
|
+
if (erroCleanup)
|
|
261
|
+
throw Error(erroCleanup);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return { executar, fechar };
|
|
265
|
+
}
|