@orkastery/cli 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/openclaw/README.md +0 -1
- package/adapters/openclaw/dist/index.js +0 -19
- package/adapters/openclaw/openclaw.plugin.json +1 -3
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/index.ts +0 -21
- package/dist/adapters/claude-bg.js +82 -6
- package/dist/adapters/codex-events.js +3 -1
- package/dist/conducao.js +46 -13
- package/dist/fix.js +1 -1
- package/dist/gates.js +1 -0
- package/dist/index.js +3 -3
- package/dist/mcp-git.js +32 -10
- package/dist/ratelimit.js +3 -2
- package/dist/retry.js +179 -1
- package/dist/runtime-ambiente.js +12 -1
- package/dist/session-watcher-claude.js +26 -0
- package/dist/sessoes.js +49 -15
- package/dist/verify.js +1 -1
- package/dist/worktree.js +77 -0
- package/package.json +1 -1
|
@@ -57,7 +57,6 @@ fallback: usa `ORK_BIN` do ambiente ou o `ork` do PATH.
|
|
|
57
57
|
| `ork_claims_add` | Registra alegacao verificavel com o comando que a comprova |
|
|
58
58
|
| `ork_verify` / `ork_verify_baseline` | Reexecuta no HEAD real; grava a baseline antes do GO |
|
|
59
59
|
| `ork_worktree_ensure` / `ork_worktree_audit` | Worktree isolada, conferida no proprio git |
|
|
60
|
-
| `ork_objective_status` / `ork_objective_message` | Aposentadas com o `ork objective` na I-43: devolvem a recusa tipada `objective.aposentado` |
|
|
61
60
|
| `ork_portfolio_list` | Lista produtos, projetos e iniciativas canônicos |
|
|
62
61
|
| `ork_gate_answer` | Resposta humana correlacionada a um gate |
|
|
63
62
|
| `ork_session_answer` | Resposta humana correlacionada a uma sessão |
|
|
@@ -254,25 +254,6 @@ const FERRAMENTAS = [
|
|
|
254
254
|
parameters: schema({ thread: { type: 'string' } }),
|
|
255
255
|
argv: (p) => ['phase', 'list', texto(p, 'thread')],
|
|
256
256
|
},
|
|
257
|
-
{
|
|
258
|
-
name: 'ork_objective_status',
|
|
259
|
-
description: 'Aposentado na I-43 junto com ork objective: devolve a recusa tipada objective.aposentado, com saida != 0. Estado de uma entrega: ork_thread_status. Ciclos ligados a um produto, projeto ou iniciativa: ork portfolio inspect <id> --json.',
|
|
260
|
-
parameters: schema({ objective: { type: 'string', description: 'ID obj-* do ticket' } }),
|
|
261
|
-
argv: (p) => ['objective', 'status', texto(p, 'objective'), '--json'],
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
name: 'ork_objective_message',
|
|
265
|
-
description: 'Aposentado na I-43 junto com ork objective: devolve a recusa tipada objective.aposentado, com saida != 0, e nao grava nada. Resposta do dono a uma pausa vai por ork_gate_answer ou ork_session_answer.',
|
|
266
|
-
parameters: schema({
|
|
267
|
-
objective: { type: 'string', description: 'ID obj-* do ticket' },
|
|
268
|
-
author: { type: 'string', description: 'Identidade fornecida pelo gateway autenticado' },
|
|
269
|
-
message: { type: 'string', description: 'Texto integral recebido do dono' },
|
|
270
|
-
}),
|
|
271
|
-
argv: (p) => [
|
|
272
|
-
'objective', 'message', texto(p, 'objective'),
|
|
273
|
-
'--text', texto(p, 'message'), '--por', texto(p, 'author'), '--role', 'human',
|
|
274
|
-
],
|
|
275
|
-
},
|
|
276
257
|
{
|
|
277
258
|
name: 'ork_portfolio_list',
|
|
278
259
|
description: 'Lista produtos, projetos e iniciativas do catálogo canônico usado pelo Kanban.',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "orkastery",
|
|
3
3
|
"name": "Orkastery",
|
|
4
4
|
"description": "Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.3",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {},
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
"ork_thread_status",
|
|
22
22
|
"ork_phase_run",
|
|
23
23
|
"ork_phase_list",
|
|
24
|
-
"ork_objective_status",
|
|
25
|
-
"ork_objective_message",
|
|
26
24
|
"ork_portfolio_list",
|
|
27
25
|
"ork_brain_status",
|
|
28
26
|
"ork_brain_query",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkastery/openclaw-plugin",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Plugin Orkastery para OpenClaw:
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Plugin Orkastery para OpenClaw: 23 tools ork_* que traduzem intencao em chamada de CLI do nucleo `ork`. Zero regra de negocio no host.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": true,
|
|
7
7
|
"license": "MIT",
|
|
@@ -265,27 +265,6 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
265
265
|
parameters: schema({ thread: { type: 'string' } }),
|
|
266
266
|
argv: (p) => ['phase', 'list', texto(p, 'thread')],
|
|
267
267
|
},
|
|
268
|
-
{
|
|
269
|
-
name: 'ork_objective_status',
|
|
270
|
-
description:
|
|
271
|
-
'Aposentado na I-43 junto com ork objective: devolve a recusa tipada objective.aposentado, com saida != 0. Estado de uma entrega: ork_thread_status. Ciclos ligados a um produto, projeto ou iniciativa: ork portfolio inspect <id> --json.',
|
|
272
|
-
parameters: schema({ objective: { type: 'string', description: 'ID obj-* do ticket' } }),
|
|
273
|
-
argv: (p) => ['objective', 'status', texto(p, 'objective'), '--json'],
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: 'ork_objective_message',
|
|
277
|
-
description:
|
|
278
|
-
'Aposentado na I-43 junto com ork objective: devolve a recusa tipada objective.aposentado, com saida != 0, e nao grava nada. Resposta do dono a uma pausa vai por ork_gate_answer ou ork_session_answer.',
|
|
279
|
-
parameters: schema({
|
|
280
|
-
objective: { type: 'string', description: 'ID obj-* do ticket' },
|
|
281
|
-
author: { type: 'string', description: 'Identidade fornecida pelo gateway autenticado' },
|
|
282
|
-
message: { type: 'string', description: 'Texto integral recebido do dono' },
|
|
283
|
-
}),
|
|
284
|
-
argv: (p) => [
|
|
285
|
-
'objective', 'message', texto(p, 'objective'),
|
|
286
|
-
'--text', texto(p, 'message'), '--por', texto(p, 'author'), '--role', 'human',
|
|
287
|
-
],
|
|
288
|
-
},
|
|
289
268
|
{
|
|
290
269
|
name: 'ork_portfolio_list',
|
|
291
270
|
description: 'Lista produtos, projetos e iniciativas do catálogo canônico usado pelo Kanban.',
|
|
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.JANELA_CURTA_MAX_MS = exports.NOME_ADAPTER = void 0;
|
|
46
46
|
exports.ambienteDoPerfil = ambienteDoPerfil;
|
|
47
|
+
exports.semContextoDeDespacho = semContextoDeDespacho;
|
|
47
48
|
exports.disponivel = disponivel;
|
|
48
49
|
exports.versao = versao;
|
|
49
50
|
exports.montarComando = montarComando;
|
|
@@ -62,6 +63,8 @@ exports.limparAnsi = limparAnsi;
|
|
|
62
63
|
exports.logs = logs;
|
|
63
64
|
exports.logsDaSessao = logsDaSessao;
|
|
64
65
|
exports.parar = parar;
|
|
66
|
+
exports.pararAchada = pararAchada;
|
|
67
|
+
exports.acharSessaoNasContas = acharSessaoNasContas;
|
|
65
68
|
exports.ocupacaoDeContexto = ocupacaoDeContexto;
|
|
66
69
|
const util_1 = require("../util");
|
|
67
70
|
const runtime_ambiente_1 = require("../runtime-ambiente");
|
|
@@ -82,6 +85,14 @@ function ambienteDoPerfil(perfil) {
|
|
|
82
85
|
return (0, runtime_profiles_1.ambienteComPerfil)((0, runtime_ambiente_1.ambienteDeAssinatura)(), perfil);
|
|
83
86
|
}
|
|
84
87
|
exports.NOME_ADAPTER = 'claude-bg';
|
|
88
|
+
/** RM-037 (defeitosdeco D-4): o ambiente do `claude --bg` sem canal nem chave do contexto do despacho. */
|
|
89
|
+
function semContextoDeDespacho(base, contexto) {
|
|
90
|
+
const env = { ...base };
|
|
91
|
+
delete env.ORK_CANAL;
|
|
92
|
+
for (const chave of Object.keys(contexto ?? {}))
|
|
93
|
+
delete env[chave];
|
|
94
|
+
return env;
|
|
95
|
+
}
|
|
85
96
|
/** Consultas do perfil interactive; gates e respostas humanas nunca recebem grant. */
|
|
86
97
|
const CONSULTAS_MCP = [
|
|
87
98
|
'mcp__orkastery__ork_thread_status',
|
|
@@ -129,6 +140,13 @@ function montarComando(pedido) {
|
|
|
129
140
|
args.push('--model', pedido.model);
|
|
130
141
|
if (pedido.effort)
|
|
131
142
|
args.push('--effort', pedido.effort);
|
|
143
|
+
// RM-037 (defeitosdeco D-4): o contexto do despacho (identidade, thread, canal) chega POR SESSAO.
|
|
144
|
+
// O `claude --bg` entrega a sessao a um processo reserva do daemon da conta, e o daemon guarda o
|
|
145
|
+
// ambiente de quem o iniciou: pelo ambiente, a sessao nascia com a identidade de outro despacho.
|
|
146
|
+
// Medido em 28/09/2026 (2.1.284): o `env` de `--settings` chega ao Bash da sessao e vence o herdado.
|
|
147
|
+
const doDespacho = pedido.ambienteExtra ?? {};
|
|
148
|
+
if (Object.keys(doDespacho).length)
|
|
149
|
+
args.push('--settings', JSON.stringify({ env: doDespacho }));
|
|
132
150
|
if (pedido.contextoRuntime) {
|
|
133
151
|
if (pedido.contextoRuntime.host !== 'claude-code')
|
|
134
152
|
throw Error('runtime.context.invalid: host Claude esperado');
|
|
@@ -255,8 +273,10 @@ function acharSessao(chave, sessoes) {
|
|
|
255
273
|
function despachar(pedido) {
|
|
256
274
|
const comando = montarComando(pedido);
|
|
257
275
|
let ambiente;
|
|
276
|
+
// D-4: nada do contexto do despacho vai ao ambiente do processo `claude`, para um daemon novo nunca
|
|
277
|
+
// nascer com ele; a identidade herdada ja sai em `ambienteDeAssinatura`, e o canal sai aqui.
|
|
258
278
|
try {
|
|
259
|
-
ambiente =
|
|
279
|
+
ambiente = semContextoDeDespacho(ambienteDoPerfil(pedido.perfil), pedido.ambienteExtra);
|
|
260
280
|
}
|
|
261
281
|
catch (e) {
|
|
262
282
|
return { ok: false, comando, sessionId: null, verificada: false, stdout: '', stderr: '', erro: e.message };
|
|
@@ -497,6 +517,13 @@ const FRASES_DE_AUTH_AUSENTE = [
|
|
|
497
517
|
* so vale como auth ausente quando o mesmo texto nao traz frase de cota: a cota, mais especifica, vence.
|
|
498
518
|
*/
|
|
499
519
|
const FRASES_AMPLAS_DE_AUTH = [/\bcodex login\b/i];
|
|
520
|
+
/**
|
|
521
|
+
* RM-037 (defeitosdeco D-6): modelo inexistente ou sem acesso na conta. Medido na transcricao da
|
|
522
|
+
* sessao af32834f (28/09/2026, `fable-5-1` num perfil de conta sem esse modelo): `error: "model_not_found"` e o texto
|
|
523
|
+
* "There's an issue with the selected model (fable-5-1). It may not exist or you may not have access
|
|
524
|
+
* to it". Frase de sobrecarga ("model is overloaded") nao entra: e falha transitoria da infra.
|
|
525
|
+
*/
|
|
526
|
+
const FRASES_DE_MODELO_INACESSIVEL = [/\bmodel_not_found\b/i, /issue with the selected model/i];
|
|
500
527
|
/**
|
|
501
528
|
* I-33 (D16): CRITERIO UNICO entre ESGOTAMENTO da conta e RATE LIMIT comum. Decisao do dono em
|
|
502
529
|
* 19/09/2026 (opcao a do A3 do CHECK aa279e17): esgotamento de cota, credito ou limite do plano
|
|
@@ -593,6 +620,9 @@ function naturezaDoLimite(saida, agoraMs = Date.now()) {
|
|
|
593
620
|
}
|
|
594
621
|
function parseFalhaDeConta(saida, agoraMs = Date.now()) {
|
|
595
622
|
const texto = saida ?? '';
|
|
623
|
+
const modelo = FRASES_DE_MODELO_INACESSIVEL.find((r) => r.test(texto));
|
|
624
|
+
if (modelo)
|
|
625
|
+
return { motivo: 'runtime.model-unavailable', resetEm: null, fonte: 'sem-horario', trecho: trechoDa(texto, modelo) };
|
|
596
626
|
const auth = FRASES_DE_AUTH_AUSENTE.find((r) => r.test(texto));
|
|
597
627
|
if (auth)
|
|
598
628
|
return { motivo: 'runtime.auth-missing', resetEm: null, fonte: 'sem-horario', trecho: trechoDa(texto, auth) };
|
|
@@ -645,9 +675,15 @@ function conferirAuth(perfil) {
|
|
|
645
675
|
}
|
|
646
676
|
return { ok: true, detalhe: `claude auth status: loggedIn (${metodo})` };
|
|
647
677
|
}
|
|
648
|
-
/**
|
|
649
|
-
|
|
650
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Comando de anexar a sessao (precisa de TTY, entao o CLI imprime em vez de executar). RM-037
|
|
680
|
+
* (defeitosdeco D-2): sessao de um perfil so e anexada com o `CLAUDE_CONFIG_DIR` daquela conta.
|
|
681
|
+
*/
|
|
682
|
+
function comandoAttach(sessionId, configDir) {
|
|
683
|
+
if (!configDir)
|
|
684
|
+
return `claude attach ${sessionId}`;
|
|
685
|
+
const dir = /^[A-Za-z0-9_./-]+$/.test(configDir) ? configDir : `'${configDir.replace(/'/g, `'\\''`)}'`;
|
|
686
|
+
return `CLAUDE_CONFIG_DIR=${dir} claude attach ${sessionId}`;
|
|
651
687
|
}
|
|
652
688
|
/**
|
|
653
689
|
* Transforma o dump de tela que o `claude logs` devolve em texto legivel.
|
|
@@ -736,10 +772,50 @@ function logsDaSessao(id, linhas = 60, ambiente = (0, runtime_ambiente_1.ambient
|
|
|
736
772
|
/** Para uma sessao em background (o `claude stop` tambem trabalha com o id curto). */
|
|
737
773
|
function parar(chave, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
738
774
|
const sessao = acharSessao(chave, listarSessoes(undefined, true, ambiente));
|
|
739
|
-
|
|
740
|
-
|
|
775
|
+
return pararAchada(sessao ?? { sessionId: chave }, ambiente);
|
|
776
|
+
}
|
|
777
|
+
/** Para a sessao ja achada numa conta, com o ambiente dessa conta, sem listar de novo. */
|
|
778
|
+
function pararAchada(sessao, ambiente = (0, runtime_ambiente_1.ambienteDeAssinatura)()) {
|
|
779
|
+
const r = exec('claude', ['stop', sessao.id ?? sessao.sessionId.slice(0, 8)], undefined, 60000, ambiente);
|
|
741
780
|
return { ok: r.ok, texto: limparAnsi(r.stdout + r.stderr).trim() };
|
|
742
781
|
}
|
|
782
|
+
/**
|
|
783
|
+
* RM-037 (defeitosdeco D-2): o `claude agents` so lista a sessao para a conta que a despachou, entao
|
|
784
|
+
* uma chave e procurada no ambiente do processo e em cada perfil claude-bg informado, sem repetir
|
|
785
|
+
* diretorio efetivo. Consulta que falha nao vira "nao encontrada": vai para `falhas`.
|
|
786
|
+
*/
|
|
787
|
+
function acharSessaoNasContas(chave, perfis) {
|
|
788
|
+
const achadas = [], falhas = [], vistos = new Set();
|
|
789
|
+
// GO-FIX (R5a): cada consulta pode custar dezenas de segundos sob carga. UUID completo e unico em
|
|
790
|
+
// qualquer conta, entao a busca para na primeira que acha; prefixo ou nome consulta todas, para
|
|
791
|
+
// recusar a chave ambigua.
|
|
792
|
+
const unica = UUID_CLAUDE.test(chave);
|
|
793
|
+
for (const perfil of [null, ...perfis.filter(p => p.runtime === 'claude-bg')]) {
|
|
794
|
+
let ambiente, configDir;
|
|
795
|
+
try {
|
|
796
|
+
ambiente = ambienteDoPerfil(perfil);
|
|
797
|
+
configDir = path.resolve((0, runtime_profiles_1.diretorioEfetivo)('claude-bg', perfil, ambiente));
|
|
798
|
+
}
|
|
799
|
+
catch (e) {
|
|
800
|
+
falhas.push(`${perfil?.id ?? 'processo'}: ${e.message}`);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (vistos.has(configDir))
|
|
804
|
+
continue;
|
|
805
|
+
vistos.add(configDir);
|
|
806
|
+
const consulta = consultarSessoes(undefined, true, ambiente);
|
|
807
|
+
if (!consulta.ok) {
|
|
808
|
+
falhas.push(`${perfil?.id ?? 'processo'}: ${consulta.detalhe}`);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const sessao = acharSessao(chave, consulta.sessoes);
|
|
812
|
+
if (sessao)
|
|
813
|
+
achadas.push({ sessao, perfil, configDir, ambiente });
|
|
814
|
+
if (sessao && unica)
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
return { achadas, falhas };
|
|
818
|
+
}
|
|
743
819
|
/**
|
|
744
820
|
* Ocupacao da janela de contexto de uma sessao, entre 0 e 1.
|
|
745
821
|
*
|
|
@@ -12,7 +12,9 @@ const falha = () => ({ classificacao: 'gate_blocked', motivo: 'runtime.unavailab
|
|
|
12
12
|
const sucesso = () => ({ classificacao: 'fase_concluida', motivo: null });
|
|
13
13
|
const humano = () => ({ classificacao: 'human.pending', motivo: 'human.pending' });
|
|
14
14
|
/** I-33 (D12): motivos da conta so saem de erro estruturado do runtime (`falhaDeContaDoErroCodex`). */
|
|
15
|
-
const MOTIVOS_DA_CONTA = ['runtime.quota-exhausted', 'runtime.auth-missing'
|
|
15
|
+
const MOTIVOS_DA_CONTA = ['runtime.quota-exhausted', 'runtime.auth-missing',
|
|
16
|
+
// GO-FIX (R3, RM-037): o modelo inacessivel tambem so vale do erro estruturado, nunca do texto do agente.
|
|
17
|
+
'runtime.model-unavailable'];
|
|
16
18
|
function motivo(v) {
|
|
17
19
|
if (v === undefined || v === null)
|
|
18
20
|
return null;
|
package/dist/conducao.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.ErroDeConducao = exports.ESPERA_PADRAO_MS = exports.PRAZO_DA_RESERVA_MS = exports.PRAZO_DE_SESSAO_PADRAO_MS = exports.MARGEM_DO_PRAZO_MS = exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENV_CANAL = exports.ERRO_CANAL_DESCONHECIDO = exports.CANAIS_DE_CONDUCAO = exports.conducaoDoLease = void 0;
|
|
36
|
+
exports.ErroDeConducao = exports.ESPERA_PADRAO_MS = exports.PRAZO_DA_RESERVA_MS = exports.PRAZO_DE_SESSAO_PADRAO_MS = exports.MARGEM_DO_PRAZO_MS = exports.ENV_SESSAO_CLAUDE = exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENV_CANAL = exports.ERRO_CANAL_DESCONHECIDO = exports.CANAIS_DE_CONDUCAO = exports.conducaoDoLease = void 0;
|
|
37
37
|
exports.validarCanal = validarCanal;
|
|
38
38
|
exports.canalDoProcesso = canalDoProcesso;
|
|
39
39
|
exports.ambienteDaConducao = ambienteDaConducao;
|
|
@@ -86,6 +86,9 @@ const maquina_1 = require("./maquina");
|
|
|
86
86
|
const thread_1 = require("./thread");
|
|
87
87
|
const horario_1 = require("./horario");
|
|
88
88
|
const util_1 = require("./util");
|
|
89
|
+
const runtime_ambiente_1 = require("./runtime-ambiente");
|
|
90
|
+
Object.defineProperty(exports, "ENV_IDENTIDADE_DE_DESPACHO", { enumerable: true, get: function () { return runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO; } });
|
|
91
|
+
Object.defineProperty(exports, "ENV_THREAD_DO_DESPACHO", { enumerable: true, get: function () { return runtime_ambiente_1.ENV_THREAD_DO_DESPACHO; } });
|
|
89
92
|
// ---------------------------------------------------------------------------
|
|
90
93
|
// Canais (D5, D6): registro fechado, declarado pela borda.
|
|
91
94
|
// ---------------------------------------------------------------------------
|
|
@@ -146,21 +149,51 @@ function canalDoProcesso(explicito, ambiente = process.env) {
|
|
|
146
149
|
return 'hermes';
|
|
147
150
|
return 'cli';
|
|
148
151
|
}
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
// Identidade de despacho (D4): a reentrada da sessao que ja conduz.
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
/** Identidade do despacho no ambiente da sessao filha, para as chamadas do CLI de dentro dela. */
|
|
153
|
-
exports.ENV_IDENTIDADE_DE_DESPACHO = 'ORK_DISPATCH_ID';
|
|
154
|
-
exports.ENV_THREAD_DO_DESPACHO = 'ORK_DISPATCH_THREAD';
|
|
155
152
|
/** O que o despacho poe no ambiente da sessao filha: identidade, thread e canal de origem. */
|
|
156
153
|
function ambienteDaConducao(identidade, threadId, canal) {
|
|
157
|
-
return { [
|
|
154
|
+
return { [runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO]: identidade, [runtime_ambiente_1.ENV_THREAD_DO_DESPACHO]: threadId, [exports.ENV_CANAL]: canal };
|
|
158
155
|
}
|
|
159
|
-
/**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
/** Variavel que o Claude Code poe em cada sessao, com o UUID dela; nao vem do daemon. */
|
|
157
|
+
exports.ENV_SESSAO_CLAUDE = 'CLAUDE_CODE_SESSION_ID';
|
|
158
|
+
/**
|
|
159
|
+
* A identidade de despacho deste processo, quando ela e desta thread.
|
|
160
|
+
*
|
|
161
|
+
* RM-037 (defeitosdeco D-4): dentro de uma sessao Claude Code, o ambiente pode trazer o par de OUTRO
|
|
162
|
+
* despacho (o daemon do `claude --bg` guardava o do primeiro despacho da conta e o passava as sessoes
|
|
163
|
+
* reserva). Com `raiz`, a sessao se reconhece pelo ledger: vale o ultimo `phase_dispatch` desta thread
|
|
164
|
+
* com o `sessionId` dela. O par do ambiente so vale se bater com esse despacho; par que aponta para um
|
|
165
|
+
* despacho claude-bg de outra sessao e recusado, para nunca reentrar a conducao de quem nao e. Fora de
|
|
166
|
+
* sessao Claude, ou com despacho codex (processo proprio por despacho), vale o ambiente, como antes.
|
|
167
|
+
*/
|
|
168
|
+
function identidadeDoAmbiente(threadId, ambiente = process.env, raiz) {
|
|
169
|
+
const id = (ambiente[runtime_ambiente_1.ENV_IDENTIDADE_DE_DESPACHO] ?? '').trim();
|
|
170
|
+
const thread = (ambiente[runtime_ambiente_1.ENV_THREAD_DO_DESPACHO] ?? '').trim();
|
|
171
|
+
const doAmbiente = /^[a-f0-9-]{36}$/.test(id) && thread === threadId ? id : null;
|
|
172
|
+
const sessao = (ambiente[exports.ENV_SESSAO_CLAUDE] ?? '').trim();
|
|
173
|
+
if (!raiz || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(sessao))
|
|
174
|
+
return doAmbiente;
|
|
175
|
+
let eventos;
|
|
176
|
+
try {
|
|
177
|
+
eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, threadId));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return doAmbiente;
|
|
181
|
+
}
|
|
182
|
+
const despachoId = (e) => e.identidade?.dispatchId;
|
|
183
|
+
const despachos = eventos.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada);
|
|
184
|
+
const doPar = doAmbiente ? despachos.filter(e => despachoId(e) === doAmbiente).at(-1) : undefined;
|
|
185
|
+
// GO-FIX (R2 do CHECK): o codex despachado de dentro de uma sessao Claude herda o
|
|
186
|
+
// CLAUDE_CODE_SESSION_ID dela, mas o par do ambiente e do proprio processo codex, por despacho.
|
|
187
|
+
if (doPar?.runtime === 'codex')
|
|
188
|
+
return doAmbiente;
|
|
189
|
+
const daSessao = despachos.filter(e => e.sessionId === sessao && typeof despachoId(e) === 'string').at(-1);
|
|
190
|
+
if (daSessao)
|
|
191
|
+
return String(despachoId(daSessao));
|
|
192
|
+
if (!doAmbiente)
|
|
193
|
+
return null;
|
|
194
|
+
if (doPar && (doPar.runtime ?? 'claude-bg') === 'claude-bg' && typeof doPar.sessionId === 'string' && doPar.sessionId !== sessao)
|
|
195
|
+
return null;
|
|
196
|
+
return doAmbiente;
|
|
164
197
|
}
|
|
165
198
|
// ---------------------------------------------------------------------------
|
|
166
199
|
// Prazos (D3): derivados do teto real da operacao; metadado, nunca prova.
|
package/dist/fix.js
CHANGED
|
@@ -196,7 +196,7 @@ function sobConducao(carregado, threadId, operacao, conducao, executar) {
|
|
|
196
196
|
canal: conducao.canal ?? (0, conducao_1.canalDoProcesso)(),
|
|
197
197
|
correlacao: conducao.correlacao ?? null,
|
|
198
198
|
operacao,
|
|
199
|
-
identidade: conducao.identidade ?? (0, conducao_1.identidadeDoAmbiente)(threadId),
|
|
199
|
+
identidade: conducao.identidade ?? (0, conducao_1.identidadeDoAmbiente)(threadId, process.env, carregado.raiz),
|
|
200
200
|
prazoMs: (0, conducao_1.prazoDaVerificacao)(carregado.manifesto.verify.timeout_ms ?? 10 * 60 * 1000, 8),
|
|
201
201
|
esperarMs: conducao.esperarMs,
|
|
202
202
|
}, executar);
|
package/dist/gates.js
CHANGED
|
@@ -44,6 +44,7 @@ exports.DESCRICAO_DO_MOTIVO = {
|
|
|
44
44
|
'runtime.rate-limited': 'o runtime bateu o limite de uso da assinatura e a fase morreu antes de terminar (bloco B3: entra na fila duravel e e retomada na janela seguinte)',
|
|
45
45
|
'runtime.quota-exhausted': 'a cota ou os creditos da conta do runtime acabaram (I-33: o perfil sai do rodizio e a fase segue em outro perfil, outro runtime ou na fila)',
|
|
46
46
|
'runtime.auth-missing': 'a conta do runtime nao esta autenticada (I-33: o perfil nunca recebe despacho ate o login ser refeito pelo proprio CLI)',
|
|
47
|
+
'runtime.model-unavailable': 'o modelo pedido nao existe ou a conta nao tem acesso a ele (RM-037: o perfil segue no rodizio e a fase vai a outro perfil com o mesmo modelo ou ao fallback do bloco)',
|
|
47
48
|
'cost.violation': 'o despacho seria redirecionado para provider pago (violacao de custo: e o unico motivo que NUNCA recebe retry automatico)',
|
|
48
49
|
'tree.blocked': 'a arvore de destino nao esta disponivel para o merge (branch em check-out com alteracao nao commitada, ou worktree que nao pode ser montada)',
|
|
49
50
|
'lease.busy': 'lease ocupado por outra thread',
|
package/dist/index.js
CHANGED
|
@@ -1221,17 +1221,17 @@ function comandoSessions(args) {
|
|
|
1221
1221
|
}
|
|
1222
1222
|
if (sub === 'logs') {
|
|
1223
1223
|
const n = Number(texto(args.opcoes.linhas) ?? '60');
|
|
1224
|
-
const r = (0, sessoes_2.logsDaSessao)(chave, Number.isFinite(n) ? n : 60);
|
|
1224
|
+
const r = (0, sessoes_2.logsDaSessao)(chave, Number.isFinite(n) ? n : 60, carregado ? raiz : null);
|
|
1225
1225
|
console.log(r.texto);
|
|
1226
1226
|
return r.codigo;
|
|
1227
1227
|
}
|
|
1228
1228
|
if (sub === 'stop') {
|
|
1229
|
-
const r = (0, sessoes_2.pararSessao)(chave);
|
|
1229
|
+
const r = (0, sessoes_2.pararSessao)(chave, carregado ? raiz : null);
|
|
1230
1230
|
console.log(r.texto);
|
|
1231
1231
|
return r.codigo;
|
|
1232
1232
|
}
|
|
1233
1233
|
if (sub === 'attach') {
|
|
1234
|
-
const r = (0, sessoes_2.comandoDeAttach)(chave);
|
|
1234
|
+
const r = (0, sessoes_2.comandoDeAttach)(chave, carregado ? raiz : null);
|
|
1235
1235
|
console.log(r.texto);
|
|
1236
1236
|
return r.codigo;
|
|
1237
1237
|
}
|
package/dist/mcp-git.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.HOOKS_ESTRITOS = exports.HOOKS_DO_COMMIT = exports.HOOKS_INERTES_DO_COMMIT = void 0;
|
|
36
37
|
exports.estadoGitMcp = estadoGitMcp;
|
|
37
38
|
exports.commitMcp = commitMcp;
|
|
38
39
|
exports.perfilGitMcp = perfil;
|
|
@@ -41,8 +42,8 @@ exports.caminhoFisicoGitMcp = fisico;
|
|
|
41
42
|
exports.metadadosGitMcp = arvoreMetadados;
|
|
42
43
|
exports.configuracaoPassivaGitMcp = configPassiva;
|
|
43
44
|
exports.ambienteGitMcp = ambiente;
|
|
44
|
-
/** Commit estreito no núcleo instalado. Repositórios com hooks/helpers
|
|
45
|
-
* explicitamente indisponíveis; nenhum hook ou configuração é desativado. */
|
|
45
|
+
/** Commit estreito no núcleo instalado. Repositórios com hooks/helpers que as operações
|
|
46
|
+
* executariam ficam explicitamente indisponíveis; nenhum hook ou configuração é desativado. */
|
|
46
47
|
const fs = __importStar(require("node:fs"));
|
|
47
48
|
const path = __importStar(require("node:path"));
|
|
48
49
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -130,6 +131,10 @@ function configPassiva(chave, valor, credenciaisInertes = false) {
|
|
|
130
131
|
return valor.length <= 8192 && !valor.includes('\0');
|
|
131
132
|
if (['user.name', 'user.email'].includes(chave))
|
|
132
133
|
return !!valor.trim() && valor.length <= 512 && !/[\x00-\x1f]/.test(valor);
|
|
134
|
+
// RM-037 (defeitosdeco D-3): a seção do próprio produto (por exemplo `orkastery.cortefeito`, lida
|
|
135
|
+
// pela trava do corte no pre-push). O Git nunca lê essa seção, então ela não executa nada.
|
|
136
|
+
if (/^orkastery\.[a-z0-9-]+$/.test(chave))
|
|
137
|
+
return valor.length <= 512 && !/[\x00-\x1f\x7f]/.test(valor);
|
|
133
138
|
if (['core.filemode', 'core.logallrefupdates', 'core.ignorecase', 'core.precomposeunicode'].includes(chave))
|
|
134
139
|
return /^(true|false)$/.test(valor);
|
|
135
140
|
if (chave === 'core.repositoryformatversion')
|
|
@@ -195,7 +200,22 @@ function atributosPassivos(wt, id, selecionados) {
|
|
|
195
200
|
}
|
|
196
201
|
return resultados.join('\0');
|
|
197
202
|
}
|
|
198
|
-
|
|
203
|
+
/**
|
|
204
|
+
* RM-037 (defeitosdeco D-3): hooks que os comandos Git do status e do commit (`config`, `ls-files`,
|
|
205
|
+
* `check-attr`, `rev-parse`, `symbolic-ref`, `diff`, `diff-tree`, `add` e `commit` sem `--amend`)
|
|
206
|
+
* nunca invocam. Lista positiva: nome fora dela (`pre-commit`, `commit-msg`, `reference-transaction`,
|
|
207
|
+
* `post-index-change`, nome que o Git criar amanhã) continua recusado, e nada é desativado.
|
|
208
|
+
*/
|
|
209
|
+
exports.HOOKS_INERTES_DO_COMMIT = new Set(['pre-push', 'pre-rebase', 'post-checkout', 'post-merge',
|
|
210
|
+
'pre-receive', 'update', 'proc-receive', 'post-receive', 'post-update', 'push-to-checkout', 'applypatch-msg',
|
|
211
|
+
'pre-applypatch', 'post-applypatch', 'sendemail-validate', 'pre-merge-commit', 'post-rewrite',
|
|
212
|
+
'p4-changelist', 'p4-prepare-changelist', 'p4-post-changelist', 'p4-pre-submit']);
|
|
213
|
+
exports.HOOKS_DO_COMMIT = { inertes: exports.HOOKS_INERTES_DO_COMMIT,
|
|
214
|
+
correcao: 'o commit do MCP o executaria; commite pelo shell da worktree, onde o hook roda como sempre' };
|
|
215
|
+
/** Padrão estrito (SHIP): merge e push executariam qualquer hook, inclusive o pre-push. */
|
|
216
|
+
exports.HOOKS_ESTRITOS = { inertes: new Set(),
|
|
217
|
+
correcao: 'o SHIP do MCP o executaria (merge ou push); entregue por PR com o bundle do ork ci prepare, ou pelo ork ship do CLI' };
|
|
218
|
+
function perfil(wt, comum, gitdir, id, selecionados, hooksDaOperacao = exports.HOOKS_ESTRITOS) {
|
|
199
219
|
fisico(path.dirname(comum), comum);
|
|
200
220
|
fisico(comum, gitdir);
|
|
201
221
|
for (const nome of ['objects', 'refs', 'logs', 'info'])
|
|
@@ -224,9 +244,11 @@ function perfil(wt, comum, gitdir, id, selecionados) {
|
|
|
224
244
|
if (fs.lstatSync(hooks, { throwIfNoEntry: false })) {
|
|
225
245
|
fisico(base, hooks);
|
|
226
246
|
for (const nome of fs.readdirSync(hooks)) {
|
|
227
|
-
const f = path.join(hooks, nome), s = fs.lstatSync(f);
|
|
228
|
-
if (!s.isFile() || s.isSymbolicLink()
|
|
229
|
-
falha(
|
|
247
|
+
const f = path.join(hooks, nome), s = fs.lstatSync(f), rotulo = nome.replace(/[^A-Za-z0-9._-]/g, '?').slice(0, 64);
|
|
248
|
+
if (!s.isFile() || s.isSymbolicLink())
|
|
249
|
+
falha(`execution-profile.unsupported: hooks preservados (${rotulo} nao e arquivo regular)`);
|
|
250
|
+
if (!nome.endsWith('.sample') && (s.mode & 0o111) !== 0 && !hooksDaOperacao.inertes.has(nome))
|
|
251
|
+
falha(`execution-profile.unsupported: hooks preservados (${rotulo}); ${hooksDaOperacao.correcao}`);
|
|
230
252
|
}
|
|
231
253
|
}
|
|
232
254
|
}
|
|
@@ -259,7 +281,7 @@ function estadoGitMcp(raiz, threadId) {
|
|
|
259
281
|
if (!match)
|
|
260
282
|
falha('worktree.invalid');
|
|
261
283
|
const comum = path.join(raiz, '.git'), gitdir = path.resolve(wt, match[1].trim());
|
|
262
|
-
perfil(wt, comum, gitdir, t.id, []);
|
|
284
|
+
perfil(wt, comum, gitdir, t.id, [], exports.HOOKS_DO_COMMIT);
|
|
263
285
|
if ((0, estado_thread_1.raizDoEstado)(wt) !== raiz)
|
|
264
286
|
falha('worktree.invalid');
|
|
265
287
|
const branch = (0, worktree_1.branchDaWorktree)(t), destino = c.manifesto.worktree.base_branch;
|
|
@@ -300,10 +322,10 @@ function executar(raiz, p) {
|
|
|
300
322
|
fisico(comum, gitdir);
|
|
301
323
|
if ((0, estado_thread_1.raizDoEstado)(wt) !== raiz)
|
|
302
324
|
falha('worktree.invalid');
|
|
303
|
-
const originalPerfil = perfil(wt, comum, gitdir, t.id, p.paths);
|
|
325
|
+
const originalPerfil = perfil(wt, comum, gitdir, t.id, p.paths, exports.HOOKS_DO_COMMIT);
|
|
304
326
|
const conferir = () => {
|
|
305
327
|
(0, mcp_artifacts_1.validarArquivosEstadoMcp)(raiz, t.id);
|
|
306
|
-
if (perfil(wt, comum, gitdir, t.id, p.paths) !== originalPerfil)
|
|
328
|
+
if (perfil(wt, comum, gitdir, t.id, p.paths, exports.HOOKS_DO_COMMIT) !== originalPerfil)
|
|
307
329
|
falha('execution-profile.changed');
|
|
308
330
|
if (git(wt, ['rev-parse', 'HEAD']).trim() !== p.expectedHead)
|
|
309
331
|
falha('head.stale');
|
|
@@ -344,7 +366,7 @@ function executar(raiz, p) {
|
|
|
344
366
|
git(wt, ['commit', '-m', p.mensagem, '--', ...p.paths]);
|
|
345
367
|
commit = git(wt, ['rev-parse', 'HEAD']).trim();
|
|
346
368
|
});
|
|
347
|
-
if (perfil(wt, comum, gitdir, t.id, p.paths) !== originalPerfil)
|
|
369
|
+
if (perfil(wt, comum, gitdir, t.id, p.paths, exports.HOOKS_DO_COMMIT) !== originalPerfil)
|
|
348
370
|
falha('execution-profile.changed');
|
|
349
371
|
estadoAuditado = (0, estado_thread_1.auditarEstado)(raiz, t.id, wt).nivel === 'ok';
|
|
350
372
|
if (!estadoAuditado)
|
package/dist/ratelimit.js
CHANGED
|
@@ -144,7 +144,7 @@ function enfileirar(carregado, novo, agoraMs = Date.now()) {
|
|
|
144
144
|
* ela, a janela padrao do manifesto (estimativa declarada, nunca chute). Auth ausente: sem prazo.
|
|
145
145
|
*/
|
|
146
146
|
function prazoDaConta(manifesto, falha, agoraMs = Date.now()) {
|
|
147
|
-
if (falha.motivo
|
|
147
|
+
if (falha.motivo !== 'runtime.quota-exhausted')
|
|
148
148
|
return null;
|
|
149
149
|
// A10: horario dito que ja passou nao devolve o perfil ao rodizio na hora: vale a janela padrao.
|
|
150
150
|
if (falha.resetEm !== null && Date.parse(falha.resetEm) > agoraMs)
|
|
@@ -153,7 +153,8 @@ function prazoDaConta(manifesto, falha, agoraMs = Date.now()) {
|
|
|
153
153
|
}
|
|
154
154
|
/** Marca no store o perfil que falhou pela conta. Sem perfil (ambiente do processo), nada a marcar. */
|
|
155
155
|
function marcarContaDaFalha(carregado, perfil, falha, agoraMs = Date.now()) {
|
|
156
|
-
|
|
156
|
+
// RM-037 (defeitosdeco D-6): modelo inacessivel nao e falha da conta inteira; o perfil fica no rodizio.
|
|
157
|
+
if (!perfil || falha.motivo === 'runtime.model-unavailable')
|
|
157
158
|
return null;
|
|
158
159
|
const esgotado = falha.motivo === 'runtime.quota-exhausted';
|
|
159
160
|
return (0, runtime_profiles_1.marcarFalhaDePerfil)(carregado.raiz, perfil.id, { estado: esgotado ? 'esgotado' : 'sem-auth',
|
package/dist/retry.js
CHANGED
|
@@ -93,6 +93,7 @@ const policies_1 = require("./policies");
|
|
|
93
93
|
const ratelimit_1 = require("./ratelimit");
|
|
94
94
|
const session_watcher_claude_1 = require("./session-watcher-claude");
|
|
95
95
|
const runtime_profiles_1 = require("./runtime-profiles");
|
|
96
|
+
const modos_1 = require("./modos");
|
|
96
97
|
const thread_1 = require("./thread");
|
|
97
98
|
const tokens_1 = require("./tokens");
|
|
98
99
|
const util_1 = require("./util");
|
|
@@ -241,6 +242,15 @@ exports.POLITICA_DE_RETRY = {
|
|
|
241
242
|
porque: 'a conta do runtime perdeu a autenticacao: o perfil nunca mais recebe despacho ate o login ser refeito pelo proprio CLI, e o MESMO prompt segue no proximo perfil ou no runtime de fallback',
|
|
242
243
|
correcao: 'ork accounts check confere o login de cada perfil; ork retry run <thread> rotaciona o prompt ja gravado',
|
|
243
244
|
},
|
|
245
|
+
// RM-037 (defeitosdeco D-6): o modelo e que falta, nao a conta. Repetir o mesmo modelo na mesma conta
|
|
246
|
+
// so repete a recusa; o perfil continua no rodizio para os outros modelos.
|
|
247
|
+
'runtime.model-unavailable': {
|
|
248
|
+
motivo: 'runtime.model-unavailable',
|
|
249
|
+
acao: 'reexecutar',
|
|
250
|
+
automatica: true,
|
|
251
|
+
porque: 'o modelo pedido nao existe ou a conta nao tem acesso a ele: o MESMO prompt, com o mesmo sha256, segue no proximo perfil do mesmo runtime com o mesmo modelo e depois no fallback do bloco, nunca de novo no par que ja recusou',
|
|
252
|
+
correcao: 'ork retry run <thread> troca o destino; sem destino, ork setup <modo> --bloco N --model <modelo acessivel> ou --fallback runtime:modelo',
|
|
253
|
+
},
|
|
244
254
|
'tree.blocked': {
|
|
245
255
|
motivo: 'tree.blocked',
|
|
246
256
|
acao: 'sincronizar-worktree',
|
|
@@ -1218,6 +1228,9 @@ function executarRetry(carregado, threadId, opcoes = {}) {
|
|
|
1218
1228
|
if (plano.motivo === 'runtime.quota-exhausted' || plano.motivo === 'runtime.auth-missing') {
|
|
1219
1229
|
return rotacionarConta(carregado, thread, plano, opcoes, vazio, registrarTentativa);
|
|
1220
1230
|
}
|
|
1231
|
+
if (plano.motivo === 'runtime.model-unavailable') {
|
|
1232
|
+
return trocarDestinoDoModelo(carregado, thread, plano, opcoes, vazio, registrarTentativa);
|
|
1233
|
+
}
|
|
1221
1234
|
// `reexecutar` e `escalar-esforco`: o MESMO prompt volta ao runtime.
|
|
1222
1235
|
const falho = ultimoDespachoFalho(raiz, threadId, plano.fase, plano.motivo === 'runtime.silencio');
|
|
1223
1236
|
if (!falho || typeof falho.promptPath !== 'string' || typeof falho.promptSha256 !== 'string') {
|
|
@@ -1337,7 +1350,10 @@ function rotacionarConta(carregado, thread, plano, opcoes, vazio, registrarTenta
|
|
|
1337
1350
|
else
|
|
1338
1351
|
implicitos.push(alvo.runtime);
|
|
1339
1352
|
atual = { runtime: alvo.runtime, perfil: alvo.perfil };
|
|
1340
|
-
|
|
1353
|
+
// GO-FIX (R4b): destino que recusou o MODELO sai da rotacao, mas a falha que manda (e o motivo da
|
|
1354
|
+
// fila) continua sendo a da conta; o modelo inacessivel tem troca propria (trocarDestinoDoModelo).
|
|
1355
|
+
if (ultimo.falhaDeConta?.motivo !== 'runtime.model-unavailable')
|
|
1356
|
+
falhaAtual = ultimo.falhaDeConta ?? falhaAtual;
|
|
1341
1357
|
if (tentativas >= plano.limite) {
|
|
1342
1358
|
return escalar('limite de tentativas na rotacao', `${tentativas} tentativa(s) de rotacao por ${motivo} em ${fase}, no limite de ` +
|
|
1343
1359
|
`${plano.limite} do manifesto: escalacao tipada pausa qualquer modo`);
|
|
@@ -1369,6 +1385,168 @@ function rotacionarConta(carregado, thread, plano, opcoes, vazio, registrarTenta
|
|
|
1369
1385
|
registrarTentativa(true, `fila duravel: pedido ${pedido.id} libera em ${pedido.liberaEm}`);
|
|
1370
1386
|
return { ...vazio, executada: true, fila: [pedido], redespacho: ultimo, detalhe: `pedido ${pedido.id} na fila ate ${pedido.liberaEm}` };
|
|
1371
1387
|
}
|
|
1388
|
+
/** RM-037 (defeitosdeco D-6): chave de um destino (runtime, perfil, modelo) que recusou o modelo. */
|
|
1389
|
+
function chaveDoModelo(runtime, perfil, model) {
|
|
1390
|
+
return `${runtime}|${perfil ?? ''}|${model ?? ''}`;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* D-6: todo destino que ja recusou o modelo nesta fase, lido do ledger. O gate do observador traz o
|
|
1394
|
+
* `sessionId` (o modelo vem do `phase_dispatch` dele); o do despacho sincrono, o ultimo
|
|
1395
|
+
* `phase_dispatch_failed` da fase antes dele.
|
|
1396
|
+
*/
|
|
1397
|
+
function destinosQueRecusaramOModelo(eventos, fase) {
|
|
1398
|
+
const chaves = [];
|
|
1399
|
+
const chaveDo = (origem) => {
|
|
1400
|
+
if (typeof origem.runtime !== 'string')
|
|
1401
|
+
return null;
|
|
1402
|
+
let perfil = null;
|
|
1403
|
+
try {
|
|
1404
|
+
perfil = perfilDoEvento(origem);
|
|
1405
|
+
}
|
|
1406
|
+
catch { /* perfil ilegivel nao exclui ninguem */ }
|
|
1407
|
+
return chaveDoModelo(origem.runtime, perfil?.id ?? null, typeof origem.model === 'string' ? origem.model : null);
|
|
1408
|
+
};
|
|
1409
|
+
eventos.forEach((e, i) => {
|
|
1410
|
+
// GO-FIX (R4a): a recusa sincrona do redespacho (nada rodou) so grava `phase_dispatch_failed`.
|
|
1411
|
+
if (e.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && e.fase === fase && String(e.erro ?? '').startsWith('runtime.model-unavailable')) {
|
|
1412
|
+
const chave = chaveDo(e);
|
|
1413
|
+
if (chave)
|
|
1414
|
+
chaves.push(chave);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (e.tipo !== ledger_1.TIPOS_DE_EVENTO.gateBloqueado || e.fase !== fase || e.motivo !== 'runtime.model-unavailable')
|
|
1418
|
+
return;
|
|
1419
|
+
const origem = typeof e.sessionId === 'string'
|
|
1420
|
+
? eventos.filter(d => d.tipo === ledger_1.TIPOS_DE_EVENTO.faseDespachada && d.sessionId === e.sessionId).at(-1)
|
|
1421
|
+
: eventos.slice(0, i).filter(d => d.tipo === ledger_1.TIPOS_DE_EVENTO.despachoFalhou && d.fase === fase).at(-1);
|
|
1422
|
+
const chave = origem ? chaveDo(origem) : null;
|
|
1423
|
+
if (chave)
|
|
1424
|
+
chaves.push(chave);
|
|
1425
|
+
});
|
|
1426
|
+
return chaves;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* D-6: o proximo destino do MESMO prompt depois de `runtime.model-unavailable`. Primeiro outro perfil do
|
|
1430
|
+
* mesmo runtime com o MESMO modelo (outra conta pode ter acesso), depois cada runtime da ordem de
|
|
1431
|
+
* fallback do bloco com o modelo dele. Perfil fora do rodizio (esgotado, sem login, desativado, pago)
|
|
1432
|
+
* nao entra, e nenhum destino que ja recusou volta. Sem destino, `null`.
|
|
1433
|
+
*/
|
|
1434
|
+
function proximoDestinoDoModelo(raiz, runtime, model, fallback, recusados) {
|
|
1435
|
+
const store = (0, runtime_profiles_1.lerPerfisComContas)(raiz);
|
|
1436
|
+
const utilizavel = (p) => !['desativado', 'provider-pago', 'sem-auth'].includes(p.estado) && (0, runtime_profiles_1.perfilDisponivel)(p);
|
|
1437
|
+
const mesmo = (0, runtime_profiles_1.perfisDoRuntime)(store, runtime).find(p => utilizavel(p) && !recusados.has(chaveDoModelo(runtime, p.id, model)));
|
|
1438
|
+
if (mesmo)
|
|
1439
|
+
return { runtime, perfil: (0, runtime_profiles_1.perfilDeDespacho)(mesmo), model, effort: null };
|
|
1440
|
+
for (const f of fallback) {
|
|
1441
|
+
const perfis = (0, runtime_profiles_1.perfisDoRuntime)(store, f.runtime);
|
|
1442
|
+
if (perfis.length) {
|
|
1443
|
+
const p = perfis.find(x => utilizavel(x) && !recusados.has(chaveDoModelo(f.runtime, x.id, f.model)));
|
|
1444
|
+
if (p)
|
|
1445
|
+
return { runtime: f.runtime, perfil: (0, runtime_profiles_1.perfilDeDespacho)(p), model: f.model, effort: f.effort ?? null };
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
if (!recusados.has(chaveDoModelo(f.runtime, null, f.model)))
|
|
1449
|
+
return { runtime: f.runtime, perfil: null, model: f.model, effort: f.effort ?? null };
|
|
1450
|
+
}
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
/** D-6: a correcao exata quando nenhum destino tem o modelo: o bloco do setup que conduz a fase. */
|
|
1454
|
+
function correcaoDoModelo(thread, fase) {
|
|
1455
|
+
const indice = modos_1.MODOS[thread.modo]?.blocos.findIndex(b => b.fases.includes(fase)) ?? -1;
|
|
1456
|
+
const bloco = indice >= 0 ? String(indice + 1) : 'N';
|
|
1457
|
+
return `ork setup ${thread.modo} --bloco ${bloco} --model <modelo acessivel>, ou declare no mesmo bloco --fallback runtime:modelo`;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* RM-037 (defeitosdeco D-6): troca de destino depois de `runtime.model-unavailable`. O modelo pedido nao
|
|
1461
|
+
* existe ou a conta nao tem acesso a ele (`model_not_found` na transcricao); o perfil continua no
|
|
1462
|
+
* rodizio e o MESMO prompt segue no proximo destino (`proximoDestinoDoModelo`). Producao no intervalo
|
|
1463
|
+
* escala ao humano, como na rotacao por conta. Recusa do modelo no redespacho (nada rodou) continua a
|
|
1464
|
+
* troca na mesma chamada, dentro do limite de tentativas. Sem destino, o humano recebe a correcao exata.
|
|
1465
|
+
*/
|
|
1466
|
+
function trocarDestinoDoModelo(carregado, thread, plano, opcoes, vazio, registrarTentativa) {
|
|
1467
|
+
const { raiz, manifesto } = carregado;
|
|
1468
|
+
const dir = (0, thread_1.dirThread)(raiz, thread.id);
|
|
1469
|
+
const fase = plano.fase;
|
|
1470
|
+
const motivo = 'runtime.model-unavailable';
|
|
1471
|
+
const eventos = (0, ledger_1.lerLedger)(dir);
|
|
1472
|
+
const gate = ultimaReprovacao(raiz, thread.id);
|
|
1473
|
+
const despacho = despachoDaFalha(eventos, gate, fase);
|
|
1474
|
+
const escalar = (razao, diagnostico) => {
|
|
1475
|
+
if (!opcoes.dryRun) {
|
|
1476
|
+
(0, gates_1.registrarGateBloqueado)(dir, thread.id, { gate: 'phase.dispatch', motivo: 'human.pending', modo: thread.modo, detalhe: diagnostico,
|
|
1477
|
+
diagnostico, correcao: `ork gate request ${thread.id}`, fase, origem: 'retry.modelo', pausaQualquerModo: true });
|
|
1478
|
+
(0, ledger_1.registrar)(dir, thread.id, ledger_1.TIPOS_DE_EVENTO.retryEscalado, { fase, motivo, origem: 'retry.modelo', acao: plano.acao,
|
|
1479
|
+
tentativas: plano.tentativas, limite: plano.limite, modo: thread.modo, razao });
|
|
1480
|
+
}
|
|
1481
|
+
return { ...vazio, detalhe: diagnostico };
|
|
1482
|
+
};
|
|
1483
|
+
if (!despacho || typeof despacho.promptPath !== 'string' || typeof despacho.promptSha256 !== 'string') {
|
|
1484
|
+
const detalhe = `nao ha despacho com prompt gravado para ${motivo} em ${fase}: rode a fase de novo com ork phase run`;
|
|
1485
|
+
registrarTentativa(false, detalhe);
|
|
1486
|
+
return { ...vazio, detalhe };
|
|
1487
|
+
}
|
|
1488
|
+
const runtime = typeof despacho.runtime === 'string' ? despacho.runtime : manifesto.runtime.adapter;
|
|
1489
|
+
const model = typeof despacho.model === 'string' ? despacho.model : null;
|
|
1490
|
+
const rotulo = model ?? '(padrao do runtime)';
|
|
1491
|
+
let perfil;
|
|
1492
|
+
try {
|
|
1493
|
+
perfil = typeof gate?.sessionId === 'string'
|
|
1494
|
+
? (0, runtime_profiles_1.perfilDoRegistro)(eventos, { sessionId: gate.sessionId, despachadaEm: typeof gate.despachoEm === 'string' ? gate.despachoEm : undefined })
|
|
1495
|
+
: perfilDoEvento(despacho);
|
|
1496
|
+
}
|
|
1497
|
+
catch (e) {
|
|
1498
|
+
return escalar('perfil registrado invalido', `troca de destino recusada: ${e.message}`);
|
|
1499
|
+
}
|
|
1500
|
+
if (typeof gate?.sessionId === 'string' && typeof gate.despachoEm === 'string') {
|
|
1501
|
+
const producao = producaoNoIntervalo(raiz, thread, { sessionId: gate.sessionId, fase, despachadaEm: gate.despachoEm }, eventos);
|
|
1502
|
+
if (producao.produziu) {
|
|
1503
|
+
return escalar('producao parcial com modelo inacessivel', `producao parcial com o modelo ${rotulo} inacessivel: ${producao.evidencia}; ` +
|
|
1504
|
+
'a sessao nao e redespachada sobre a worktree alterada e a fase nao conclui sem o humano');
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
const recusados = new Set([...destinosQueRecusaramOModelo(eventos, fase), chaveDoModelo(runtime, perfil?.id ?? null, model)]);
|
|
1508
|
+
const doBloco = (0, setup_1.configDoBloco)((0, setup_1.lerSetup)(raiz), thread.modo, fase);
|
|
1509
|
+
const fallback = alvosDeFallback(doBloco, doBloco?.runtime ?? manifesto.runtime.adapter);
|
|
1510
|
+
let atual = { runtime, perfil, model };
|
|
1511
|
+
let tentativas = plano.tentativas;
|
|
1512
|
+
for (;;) {
|
|
1513
|
+
const alvo = proximoDestinoDoModelo(raiz, runtime, model, fallback, recusados);
|
|
1514
|
+
if (!alvo)
|
|
1515
|
+
break;
|
|
1516
|
+
if (!opcoes.dryRun)
|
|
1517
|
+
(0, ledger_1.registrar)(dir, thread.id, ledger_1.TIPOS_DE_EVENTO.perfilRotacionado, { fase, motivo, origem: 'retry.modelo',
|
|
1518
|
+
de: { runtime: atual.runtime, perfil: atual.perfil?.id ?? null, model: atual.model },
|
|
1519
|
+
para: { runtime: alvo.runtime, perfil: alvo.perfil?.id ?? null, model: alvo.model },
|
|
1520
|
+
razao: alvo.runtime === runtime ? 'outro perfil do mesmo runtime com o mesmo modelo' : 'fallback de runtime pela ordem do bloco',
|
|
1521
|
+
promptSha256: despacho.promptSha256, modo: thread.modo,
|
|
1522
|
+
autorizadoPor: `politica de retry da RM-037 (D-6), bloco sem pausa ou delegado no modo ${thread.modo}` });
|
|
1523
|
+
const mudouRuntime = alvo.runtime !== runtime;
|
|
1524
|
+
const r = redespachar(carregado, thread, fase, despacho.promptPath, despacho.promptSha256, {
|
|
1525
|
+
runtime: alvo.runtime, perfil: alvo.perfil?.id ?? null, model: alvo.model,
|
|
1526
|
+
effort: mudouRuntime ? alvo.effort ?? (typeof despacho.effort === 'string' ? despacho.effort : null) : plano.effort,
|
|
1527
|
+
origem: 'retry.modelo', dryRun: opcoes.dryRun,
|
|
1528
|
+
});
|
|
1529
|
+
if (opcoes.dryRun)
|
|
1530
|
+
return { ...vazio, redespacho: r, detalhe: `ensaio: o mesmo prompt iria a ${alvo.runtime}` +
|
|
1531
|
+
`${alvo.perfil ? ` (perfil ${alvo.perfil.id})` : ''} com o modelo ${alvo.model ?? '(padrao do runtime)'}; ${r.detalhe}` };
|
|
1532
|
+
registrarTentativa(r.ok, r.detalhe);
|
|
1533
|
+
tentativas += 1;
|
|
1534
|
+
if (r.ok)
|
|
1535
|
+
return { ...vazio, executada: true, redespacho: r, detalhe: r.detalhe };
|
|
1536
|
+
// So a recusa do MODELO no redespacho (nada rodou) continua a troca; outra falha para aqui.
|
|
1537
|
+
if (r.falhaDeConta?.motivo !== 'runtime.model-unavailable')
|
|
1538
|
+
return { ...vazio, redespacho: r, detalhe: r.detalhe };
|
|
1539
|
+
recusados.add(chaveDoModelo(alvo.runtime, alvo.perfil?.id ?? null, alvo.model));
|
|
1540
|
+
atual = { runtime: alvo.runtime, perfil: alvo.perfil, model: alvo.model };
|
|
1541
|
+
if (tentativas >= plano.limite) {
|
|
1542
|
+
return escalar('limite de tentativas na troca de modelo', `${tentativas} tentativa(s) de troca por ${motivo} em ${fase}, no limite de ` +
|
|
1543
|
+
`${plano.limite} do manifesto: escalacao tipada pausa qualquer modo`);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const tentados = [...recusados].map(k => { const [rt, p, m] = k.split('|'); return `${rt}${p ? `/${p}` : ''}:${m || '(padrao)'}`; });
|
|
1547
|
+
return escalar('modelo inacessivel sem outro destino', `o modelo ${rotulo} nao esta acessivel em ${tentados.join(', ')} e o bloco ` +
|
|
1548
|
+
`nao tem outro destino: ${correcaoDoModelo(thread, fase)}`);
|
|
1549
|
+
}
|
|
1372
1550
|
/** Texto de `ork retry plan`. */
|
|
1373
1551
|
function textoDoPlano(p) {
|
|
1374
1552
|
const linhas = [];
|
package/dist/runtime-ambiente.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ENVS_DE_PROVIDER_PAGO = void 0;
|
|
3
|
+
exports.ENV_THREAD_DO_DESPACHO = exports.ENV_IDENTIDADE_DE_DESPACHO = exports.ENVS_DE_PROVIDER_PAGO = void 0;
|
|
4
4
|
exports.nomesDeProviderAtivos = nomesDeProviderAtivos;
|
|
5
5
|
exports.ambienteDeAssinatura = ambienteDeAssinatura;
|
|
6
6
|
const hitl_public_receipt_1 = require("./hitl-public-receipt");
|
|
@@ -20,6 +20,13 @@ exports.ENVS_DE_PROVIDER_PAGO = [
|
|
|
20
20
|
function nomesDeProviderAtivos(base = process.env) {
|
|
21
21
|
return exports.ENVS_DE_PROVIDER_PAGO.filter(nome => (base[nome] ?? '').trim() !== '');
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Identidade do despacho no ambiente da sessao filha (I-36, D4). RM-037 (defeitosdeco D-4): o domicilio
|
|
25
|
+
* dos nomes e aqui porque nenhum filho de runtime herda a identidade de quem despachou; o despacho
|
|
26
|
+
* acrescenta a sua explicitamente, e o `claude --bg` a leva por sessao em `--settings`.
|
|
27
|
+
*/
|
|
28
|
+
exports.ENV_IDENTIDADE_DE_DESPACHO = 'ORK_DISPATCH_ID';
|
|
29
|
+
exports.ENV_THREAD_DO_DESPACHO = 'ORK_DISPATCH_THREAD';
|
|
23
30
|
/** Copia, sem alterar o ambiente em que as policies ainda precisam ser avaliadas. */
|
|
24
31
|
function ambienteDeAssinatura(base = process.env) {
|
|
25
32
|
const env = { ...base };
|
|
@@ -31,6 +38,10 @@ function ambienteDeAssinatura(base = process.env) {
|
|
|
31
38
|
for (const nome of Object.keys(env))
|
|
32
39
|
if (nome.startsWith('ORK_HITL_'))
|
|
33
40
|
delete env[nome];
|
|
41
|
+
// RM-037 (defeitosdeco D-4): a identidade herdada e de OUTRO despacho (o daemon do `claude --bg`
|
|
42
|
+
// guardava a do primeiro despacho e a passava a todas as sessoes reserva da conta).
|
|
43
|
+
delete env[exports.ENV_IDENTIDADE_DE_DESPACHO];
|
|
44
|
+
delete env[exports.ENV_THREAD_DO_DESPACHO];
|
|
34
45
|
delete env[hitl_public_receipt_1.ENV_HITL_VERIFIERS];
|
|
35
46
|
if (publicVerifiers)
|
|
36
47
|
env[hitl_public_receipt_1.ENV_HITL_VERIFIERS] = publicVerifiers;
|
|
@@ -158,6 +158,20 @@ function classificarSessaoClaude(e) {
|
|
|
158
158
|
: { terminal: true, classificacao: 'fase_concluida', motivo: null, fonte: `${sinal}; ${prova.fonte}`, conclusaoNativa, stop };
|
|
159
159
|
return { ...c, dependeDoStop: true };
|
|
160
160
|
};
|
|
161
|
+
// RM-037 (defeitosdeco D-1): turno encerrado pelo Stop e sessão viva em `blocked` é o Claude Code
|
|
162
|
+
// dizendo que o texto final pediu resposta. Nunca conclui direto, e a pausa humana abre sempre
|
|
163
|
+
// (`human.pending`): GO-FIX (R6 do CHECK), sem a prova o motivo também é o humano, porque um
|
|
164
|
+
// `artifact.missing` automático abriria correção sobre uma sessão viva que espera resposta.
|
|
165
|
+
const esperaHumana = () => {
|
|
166
|
+
const sinal = 'Stop correlacionado e sessão viva à espera humana (blocked)';
|
|
167
|
+
const prova = e.prova();
|
|
168
|
+
const c = !prova.ok
|
|
169
|
+
? { ...bloqueio(`${sinal}; sem prova do ork: ${prova.fonte}; o humano decide`, 'human.pending'),
|
|
170
|
+
diagnostico: `a sessão espera resposta humana (blocked) e a fase ainda não tem a prova do ork: ${prova.fonte}` }
|
|
171
|
+
: { ...bloqueio(`${sinal}; ${prova.fonte}; o humano decide`, 'human.pending'),
|
|
172
|
+
diagnostico: 'a fase tem a prova do ork e a sessão espera resposta humana (blocked): o humano decide' };
|
|
173
|
+
return { ...c, dependeDoStop: true };
|
|
174
|
+
};
|
|
161
175
|
const venceu = (desde) => agora - desde >= e.limiteMs;
|
|
162
176
|
// Sem prova para concluir nem para bloquear: `runtime.unavailable` reexecutaria o mesmo prompt
|
|
163
177
|
// sobre uma fase que pode ter terminado. O observador encerra sem gate e o radar de liveness
|
|
@@ -183,6 +197,8 @@ function classificarSessaoClaude(e) {
|
|
|
183
197
|
delete cursor.doneDesde;
|
|
184
198
|
if (estado !== 'working' && estado !== 'blocked')
|
|
185
199
|
delete cursor.mortoDesde;
|
|
200
|
+
if (estado !== 'blocked')
|
|
201
|
+
delete cursor.bloqueadoDesde;
|
|
186
202
|
if (['done', 'failed', 'stopped', 'working', 'blocked'].includes(estado ?? ''))
|
|
187
203
|
delete cursor.desconhecidoDesde;
|
|
188
204
|
switch (estado) {
|
|
@@ -220,6 +236,16 @@ function classificarSessaoClaude(e) {
|
|
|
220
236
|
return bloqueio(`processo da sessão morreu sem terminal nativo nem Stop correlacionado (estado ${estado})`);
|
|
221
237
|
}
|
|
222
238
|
delete cursor.mortoDesde;
|
|
239
|
+
// D-1: a segunda leitura, com pelo menos um intervalo do observador, separa o `blocked` estável
|
|
240
|
+
// de um instante de transição logo depois do Stop. `status` ocupado ainda é trabalho.
|
|
241
|
+
const ocupado = ['busy', 'running', 'working'].includes(r.status ?? '');
|
|
242
|
+
if (estado === 'blocked' && stop && !bloqueioPendente(e.eventos, e.sessao) && !ocupado) {
|
|
243
|
+
cursor.bloqueadoDesde ??= agora;
|
|
244
|
+
if (agora - cursor.bloqueadoDesde < exports.INTERVALO_WATCH_CLAUDE_MS)
|
|
245
|
+
return espera('turno encerrado pelo Stop com a sessão à espera humana (blocked); aguardando a segunda observação');
|
|
246
|
+
return esperaHumana();
|
|
247
|
+
}
|
|
248
|
+
delete cursor.bloqueadoDesde;
|
|
223
249
|
return espera(estado === 'blocked' ? 'sessão bloqueada à espera humana' : 'sessão trabalhando');
|
|
224
250
|
}
|
|
225
251
|
default:
|
package/dist/sessoes.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.pararSessao = pararSessao;
|
|
|
46
46
|
exports.comandoDeAttach = comandoDeAttach;
|
|
47
47
|
const adapter = __importStar(require("./adapters/claude-bg"));
|
|
48
48
|
const sessoes_inventario_1 = require("./sessoes-inventario");
|
|
49
|
+
const runtime_profiles_1 = require("./runtime-profiles");
|
|
49
50
|
const util_1 = require("./util");
|
|
50
51
|
function textoDoInventario(r) {
|
|
51
52
|
const linhas = [
|
|
@@ -63,23 +64,56 @@ function textoDoInventario(r) {
|
|
|
63
64
|
function tabelaDeSessoes(raiz, todas = false) {
|
|
64
65
|
return textoDoInventario((0, sessoes_inventario_1.inventariarSessoes)(raiz, { todas }));
|
|
65
66
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* RM-037 (defeitosdeco D-2): a sessao pelo id curto, id completo ou nome, procurada no ambiente do
|
|
69
|
+
* processo e em cada perfil claude-bg do projeto (inclusive desativado, que ainda pode ter sessao
|
|
70
|
+
* viva). So uma conta pode responder; duas viram recusa, nenhuma mantem a mensagem de antes.
|
|
71
|
+
*/
|
|
72
|
+
function localizarSessao(chave, raiz) {
|
|
73
|
+
let perfis = [];
|
|
74
|
+
const falhas = [];
|
|
75
|
+
if (raiz) {
|
|
76
|
+
try {
|
|
77
|
+
perfis = (0, runtime_profiles_1.lerPerfis)(raiz).perfis.filter(p => p.runtime === 'claude-bg').map(runtime_profiles_1.perfilDeDespacho);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
falhas.push(`perfis: ${e.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const r = adapter.acharSessaoNasContas(chave, perfis);
|
|
84
|
+
falhas.push(...r.falhas);
|
|
85
|
+
if (r.achadas.length === 1)
|
|
86
|
+
return { achada: r.achadas[0] };
|
|
87
|
+
if (r.achadas.length > 1) {
|
|
88
|
+
const contas = r.achadas.map(a => `${a.perfil?.id ?? 'processo'} (${a.sessao.sessionId})`).join(', ');
|
|
89
|
+
return { texto: `sessao "${chave}" encontrada em mais de uma conta: ${contas}; use o id completo`, codigo: 1 };
|
|
90
|
+
}
|
|
91
|
+
return { texto: `sessao "${chave}" nao encontrada no runtime` + (falhas.length ? `; contas sem resposta: ${falhas.join('; ')}` : ''),
|
|
92
|
+
codigo: 1 };
|
|
93
|
+
}
|
|
94
|
+
/** Logs de uma sessao pelo id curto, id completo ou nome, lidos na conta onde ela esta. */
|
|
95
|
+
function logsDaSessao(chave, linhas, raiz) {
|
|
96
|
+
const l = localizarSessao(chave, raiz);
|
|
97
|
+
if (!('achada' in l))
|
|
98
|
+
return l;
|
|
99
|
+
const { sessao, ambiente } = l.achada;
|
|
100
|
+
const r = adapter.logsDaSessao(sessao.id ?? sessao.sessionId.slice(0, 8), linhas, ambiente);
|
|
69
101
|
return { texto: r.texto, codigo: r.ok ? 0 : 1 };
|
|
70
102
|
}
|
|
71
|
-
/** Para uma sessao pelo id curto, id completo ou nome. */
|
|
72
|
-
function pararSessao(chave) {
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
75
|
-
return
|
|
76
|
-
const
|
|
77
|
-
|
|
103
|
+
/** Para uma sessao pelo id curto, id completo ou nome, com o `CLAUDE_CONFIG_DIR` da conta dela. */
|
|
104
|
+
function pararSessao(chave, raiz) {
|
|
105
|
+
const l = localizarSessao(chave, raiz);
|
|
106
|
+
if (!('achada' in l))
|
|
107
|
+
return l;
|
|
108
|
+
const { sessao, perfil, ambiente } = l.achada;
|
|
109
|
+
const r = adapter.pararAchada(sessao, ambiente);
|
|
110
|
+
return { texto: r.texto || `sessao ${sessao.sessionId} parada${perfil ? ` (perfil ${perfil.id})` : ''}`, codigo: r.ok ? 0 : 1 };
|
|
78
111
|
}
|
|
79
112
|
/** Comando de attach (precisa de TTY, entao o CLI imprime em vez de executar). */
|
|
80
|
-
function comandoDeAttach(chave) {
|
|
81
|
-
const
|
|
82
|
-
if (!
|
|
83
|
-
return
|
|
84
|
-
|
|
113
|
+
function comandoDeAttach(chave, raiz) {
|
|
114
|
+
const l = localizarSessao(chave, raiz);
|
|
115
|
+
if (!('achada' in l))
|
|
116
|
+
return l;
|
|
117
|
+
const { sessao, perfil, configDir } = l.achada;
|
|
118
|
+
return { texto: adapter.comandoAttach(sessao.sessionId, perfil ? configDir : null), codigo: 0 };
|
|
85
119
|
}
|
package/dist/verify.js
CHANGED
|
@@ -191,7 +191,7 @@ function pedidoDoVerify(carregado, threadId, operacao, comandos, conducao) {
|
|
|
191
191
|
canal: conducao.canal ?? (0, conducao_1.canalDoProcesso)(),
|
|
192
192
|
correlacao: conducao.correlacao ?? null,
|
|
193
193
|
operacao,
|
|
194
|
-
identidade: conducao.identidade ?? (0, conducao_1.identidadeDoAmbiente)(threadId),
|
|
194
|
+
identidade: conducao.identidade ?? (0, conducao_1.identidadeDoAmbiente)(threadId, process.env, carregado.raiz),
|
|
195
195
|
prazoMs: (0, conducao_1.prazoDaVerificacao)(carregado.manifesto.verify.timeout_ms ?? exports.TIMEOUT_VERIFY_MS, comandos),
|
|
196
196
|
esperarMs: conducao.esperarMs,
|
|
197
197
|
};
|
package/dist/worktree.js
CHANGED
|
@@ -52,6 +52,7 @@ exports.worktreesDoGit = worktreesDoGit;
|
|
|
52
52
|
exports.registradaNoGit = registradaNoGit;
|
|
53
53
|
exports.arvoreLimpa = arvoreLimpa;
|
|
54
54
|
exports.garantirWorktree = garantirWorktree;
|
|
55
|
+
exports.historiaPropria = historiaPropria;
|
|
55
56
|
exports.sincronizarWorktree = sincronizarWorktree;
|
|
56
57
|
exports.auditarWorktree = auditarWorktree;
|
|
57
58
|
exports.textoDoAudit = textoDoAudit;
|
|
@@ -189,6 +190,34 @@ function garantirWorktree(carregado, id) {
|
|
|
189
190
|
correcao: '',
|
|
190
191
|
};
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* RM-037 (defeitosdeco D-5): o que a branch tem de proprio. Bases conhecidas da thread sao o
|
|
195
|
+
* `base.commit` carimbado na criacao e cada `shaBase` que um sync ja incorporou (lidos do ledger);
|
|
196
|
+
* commit que o git nao tem mais nao entra. `proprios` conta o que o HEAD tem fora delas e da base
|
|
197
|
+
* atual (`null` sem nenhuma base conhecida); `pontoDePartida` e a base conhecida mais nova que e
|
|
198
|
+
* ancestral do HEAD; `semAncestral` diz que HEAD e base atual nao tem historia em comum.
|
|
199
|
+
*/
|
|
200
|
+
function historiaPropria(raiz, thread, dir, shaBase) {
|
|
201
|
+
const git = (...args) => (0, util_1.exec)('git', args, dir);
|
|
202
|
+
const sincronizadas = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread.id))
|
|
203
|
+
.filter(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada && typeof e.shaBase === 'string' && e.shaBase !== '')
|
|
204
|
+
.map(e => String(e.shaBase));
|
|
205
|
+
// GO-FIX (R1): no ciclo `merge-branch`, `base.commit` e a ponta da branch que ja existia, com os
|
|
206
|
+
// commits proprios dela. Nao e base: vale so o ponto onde ela sai da base atual, quando existe.
|
|
207
|
+
const inicio = thread.base?.commit;
|
|
208
|
+
const valido = (sha) => typeof sha === 'string' && /^[a-f0-9]{40,64}$/.test(sha) &&
|
|
209
|
+
git('cat-file', '-e', `${sha}^{commit}`).ok;
|
|
210
|
+
const pontoDaCriacao = thread.variante !== 'merge-branch' ? inicio
|
|
211
|
+
: valido(inicio) ? (git('merge-base', inicio, shaBase).stdout.trim() || null) : null;
|
|
212
|
+
const conhecidas = [...new Set([pontoDaCriacao, ...sincronizadas])].filter(valido);
|
|
213
|
+
const semAncestral = git('merge-base', 'HEAD', shaBase).code === 1;
|
|
214
|
+
const pontoDePartida = [...conhecidas].reverse().find(sha => git('merge-base', '--is-ancestor', sha, 'HEAD').ok) ?? null;
|
|
215
|
+
if (!conhecidas.length)
|
|
216
|
+
return { proprios: null, pontoDePartida, semAncestral };
|
|
217
|
+
const contagem = git('rev-list', '--count', 'HEAD', '--not', ...conhecidas, shaBase);
|
|
218
|
+
const proprios = contagem.ok && /^\d+$/.test(contagem.stdout.trim()) ? Number(contagem.stdout.trim()) : null;
|
|
219
|
+
return { proprios, pontoDePartida, semAncestral };
|
|
220
|
+
}
|
|
192
221
|
/**
|
|
193
222
|
* Sincroniza a branch da thread com a base ATUAL (rebase), quando a base avancou.
|
|
194
223
|
*
|
|
@@ -287,6 +316,54 @@ function sincronizarWorktree(carregado, id, opcoes = {}) {
|
|
|
287
316
|
correcao: '',
|
|
288
317
|
};
|
|
289
318
|
}
|
|
319
|
+
// RM-037 (defeitosdeco D-5): depois do corte de 27/09 a base tem raiz nova. Branch sem commit
|
|
320
|
+
// proprio nao e rebasada: o rebase sem ancestral comum reaplicaria a historia antiga inteira. Ela
|
|
321
|
+
// e recriada no SHA da base com `reset --keep`, que aborta se houvesse mudanca local a perder (a
|
|
322
|
+
// arvore ja foi conferida limpa). Com commit proprio e sem ancestral comum, a branch fica como esta
|
|
323
|
+
// e a correcao exata reaplica so o que e dela.
|
|
324
|
+
const historia = historiaPropria(raiz, thread, dir, shaBase);
|
|
325
|
+
if (historia.proprios === 0) {
|
|
326
|
+
passos.push(`git reset --keep ${shaBase}`);
|
|
327
|
+
if (opcoes.dryRun) {
|
|
328
|
+
return { ...vazio, shaBase, shaAntes, shaDepois: shaAntes, ok: true, jaAtualizada: false, rebaseFeito: false, recriada: false,
|
|
329
|
+
motivo: null, detalhe: `a branch ${branch} nao tem commit proprio: o sync real a recriaria na base ${base} atual, sem rebase`,
|
|
330
|
+
correcao: '' };
|
|
331
|
+
}
|
|
332
|
+
let recriacao;
|
|
333
|
+
try {
|
|
334
|
+
recriacao = (0, estado_thread_1.comEstadoParaGit)(raiz, id, dir, () => (0, util_1.exec)('git', ['reset', '--keep', shaBase], dir));
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
return { ...vazio, shaBase, shaAntes, ok: false, jaAtualizada: false, rebaseFeito: false,
|
|
338
|
+
motivo: 'tree.blocked', detalhe: e.message,
|
|
339
|
+
correcao: 'inspecione a cópia local e a fonte canônica preservadas antes de repetir o sync' };
|
|
340
|
+
}
|
|
341
|
+
const shaDepois = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], dir).stdout.trim();
|
|
342
|
+
if (!recriacao.ok || shaDepois !== shaBase) {
|
|
343
|
+
return { ...vazio, shaBase, shaAntes, shaDepois, ok: false, jaAtualizada: false, rebaseFeito: false, motivo: 'tree.blocked',
|
|
344
|
+
detalhe: `a recriacao da branch ${branch} na base falhou: ${(recriacao.stderr || recriacao.stdout).trim().split('\n').slice(-2).join(' | ')}`,
|
|
345
|
+
correcao: `confira a worktree ${dir} e rode de novo: ork worktree sync ${id}` };
|
|
346
|
+
}
|
|
347
|
+
const detalhe = historia.semAncestral
|
|
348
|
+
? `branch sem commit proprio recriada a partir da base ${base} atual; a base foi reescrita e nao tinha historia em comum com ela`
|
|
349
|
+
: `branch sem commit proprio recriada a partir da base ${base} atual`;
|
|
350
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada, { dir, branch, base, shaBase, shaAntes, shaDepois,
|
|
351
|
+
rebaseFeito: false, recriada: true, semAncestral: historia.semAncestral, detalhe });
|
|
352
|
+
return { ...vazio, shaBase, shaAntes, shaDepois, ok: true, jaAtualizada: false, rebaseFeito: false, recriada: true,
|
|
353
|
+
motivo: null, detalhe, correcao: '' };
|
|
354
|
+
}
|
|
355
|
+
if (historia.semAncestral) {
|
|
356
|
+
const onto = historia.pontoDePartida
|
|
357
|
+
? `git -C ${dir} rebase --onto ${shaBase} ${historia.pontoDePartida}`
|
|
358
|
+
: `git -C ${dir} rebase --onto ${shaBase} <commit onde a branch da thread comecou>`;
|
|
359
|
+
const detalhe = `a base ${base} foi reescrita e nao tem historia em comum com a branch ${branch}, que tem ` +
|
|
360
|
+
`${historia.proprios ?? 'um numero desconhecido de'} commit(s) proprio(s): o rebase reaplicaria a historia antiga inteira`;
|
|
361
|
+
if (!opcoes.dryRun)
|
|
362
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada, { dir, branch, base, shaBase, shaAntes,
|
|
363
|
+
rebaseFeito: false, motivo: 'tree.blocked', semAncestral: true, detalhe });
|
|
364
|
+
return { ...vazio, shaBase, shaAntes, shaDepois: shaAntes, ok: false, jaAtualizada: false, rebaseFeito: false,
|
|
365
|
+
motivo: 'tree.blocked', detalhe, correcao: `reaplique so os commits da thread sobre a base nova: ${onto}` };
|
|
366
|
+
}
|
|
290
367
|
if (opcoes.dryRun) {
|
|
291
368
|
passos.push(`git rebase ${shaBase}`);
|
|
292
369
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkastery/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "A software factory of AI agents that proves its work: parallel threads, verified results, and short decisions only when they matter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/orkastery/orkastery#readme",
|