@orkastery/cli 0.6.5 → 0.6.6

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.
@@ -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. Toda tool de projeto aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap; as maquinas da pessoa, de ork_network_status (sem projeto).",
5
- "version": "0.6.5",
5
+ "version": "0.6.6",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "properties": {},
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkastery/openclaw-plugin",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Plugin Orkastery para OpenClaw: 32 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,
@@ -39,6 +39,8 @@ exports.reconciliarSessaoSemWatcher = reconciliarSessaoSemWatcher;
39
39
  exports.validarRegistroWatcher = validarRegistroWatcher;
40
40
  exports.validarFonteWatcher = validarFonteWatcher;
41
41
  exports.prepararFonteWatcher = prepararFonteWatcher;
42
+ exports.esperarProntidaoWatcher = esperarProntidaoWatcher;
43
+ exports.esperarConfirmacaoDoPaiWatcher = esperarConfirmacaoDoPaiWatcher;
42
44
  exports.iniciarWatcher = iniciarWatcher;
43
45
  exports.acompanharSessao = acompanharSessao;
44
46
  /** D6: observador restrito a uma sessão, cursor canônico e resultados idempotentes. */
@@ -906,6 +908,61 @@ function prepararFonteWatcher(raiz, sessionId, fonte) {
906
908
  throw e;
907
909
  }
908
910
  }
911
+ class ErroDoApertoWatcher extends Error {
912
+ encerrarOutro;
913
+ constructor(message, encerrarOutro) {
914
+ super(message);
915
+ this.encerrarOutro = encerrarOutro;
916
+ }
917
+ }
918
+ const TETO_ABSOLUTO_APERTO_WATCHER_MS = 60000;
919
+ const PAUSA_APERTO_WATCHER = new Int32Array(new SharedArrayBuffer(4));
920
+ function conferirTetoDoAperto(tetoMs) {
921
+ if (!Number.isFinite(tetoMs) || tetoMs <= 0)
922
+ throw new Error('teto invalido para o aperto de mao do watcher');
923
+ }
924
+ /** Lado do dispatcher: lentidao nao encerra um watcher cuja identidade continua viva. */
925
+ function esperarProntidaoWatcher(lerSinal, identidade, opcoes = {}) {
926
+ const agora = opcoes.agora ?? Date.now;
927
+ const estado = opcoes.estado ?? codex_runner_1.estadoProcesso;
928
+ const pausar = opcoes.pausar ?? ((ms) => { Atomics.wait(PAUSA_APERTO_WATCHER, 0, 0, ms); });
929
+ const tetoProntidaoMs = opcoes.tetoMs ?? TETO_ABSOLUTO_APERTO_WATCHER_MS;
930
+ conferirTetoDoAperto(tetoProntidaoMs);
931
+ const inicio = agora();
932
+ for (;;) {
933
+ if (agora() - inicio >= tetoProntidaoMs)
934
+ throw new ErroDoApertoWatcher('watcher sem prontidao confirmada', true);
935
+ const sinal = lerSinal();
936
+ if (sinal === 'confirmado')
937
+ return;
938
+ if (sinal === 'invalido')
939
+ throw new ErroDoApertoWatcher('watcher com prontidao invalida', true);
940
+ if (estado(identidade) === 'ausente')
941
+ throw new ErroDoApertoWatcher('watcher sem prontidao confirmada', false);
942
+ const restante = tetoProntidaoMs - (agora() - inicio);
943
+ pausar(Math.min(10, Math.max(1, restante)));
944
+ }
945
+ }
946
+ /** Lado do watcher: o ack continua pendente somente enquanto o pai capturado na partida nao saiu. */
947
+ async function esperarConfirmacaoDoPaiWatcher(lerSinal, identidadePai, opcoes = {}) {
948
+ const agora = opcoes.agora ?? Date.now;
949
+ const estado = opcoes.estado ?? codex_runner_1.estadoProcesso;
950
+ const pausar = opcoes.pausar ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
951
+ const tetoConfirmacaoMs = opcoes.tetoMs ?? TETO_ABSOLUTO_APERTO_WATCHER_MS;
952
+ conferirTetoDoAperto(tetoConfirmacaoMs);
953
+ const inicio = agora();
954
+ for (;;) {
955
+ if (agora() - inicio >= tetoConfirmacaoMs)
956
+ throw new ErroDoApertoWatcher('watcher sem confirmacao do pai', false);
957
+ const sinal = lerSinal();
958
+ if (sinal === 'confirmado')
959
+ return;
960
+ if (sinal === 'invalido' || estado(identidadePai) === 'ausente')
961
+ throw new ErroDoApertoWatcher('watcher sem confirmacao do pai', false);
962
+ const restante = tetoConfirmacaoMs - (agora() - inicio);
963
+ await pausar(Math.min(10, Math.max(1, restante)));
964
+ }
965
+ }
909
966
  function iniciarWatcher(raiz, sessionId) {
910
967
  const { thread, sessao: registrada } = (0, session_events_1.resolverSessao)(raiz, sessionId);
911
968
  const sessao = (0, session_events_1.exigirSessaoDespachada)(registrada, sessionId);
@@ -947,27 +1004,29 @@ function iniciarWatcher(raiz, sessionId) {
947
1004
  try {
948
1005
  if (!identidade)
949
1006
  throw new Error('watcher sem identidade de processo');
950
- const deadline = Date.now() + 5000;
951
- const pausa = new Int32Array(new SharedArrayBuffer(4));
952
- while (Date.now() < deadline) {
953
- const ready = lerJson(readyPath);
954
- if (ready && ready.token === token && ready.sessionId === sessionId && ready.despachoEm === sessao.despachadaEm &&
955
- JSON.stringify(ready.identidade) === JSON.stringify(identidade) && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo') {
956
- (0, ledger_1.registrar)(dir, thread.id, 'session_watcher_started', { fase: sessao.fase, sessionId,
957
- despachoEm: sessao.despachadaEm, pid: identidade.pid, identidade, readyPath, token });
958
- (0, codex_runner_1.gravarAtomico)(ackPath, { token });
959
- return identidade.pid;
1007
+ esperarProntidaoWatcher(() => {
1008
+ if (!fs.existsSync(readyPath))
1009
+ return 'pendente';
1010
+ let ready;
1011
+ try {
1012
+ ready = lerJson(readyPath);
960
1013
  }
961
- if ((0, codex_runner_1.estadoProcesso)(identidade) === 'ausente')
962
- break;
963
- Atomics.wait(pausa, 0, 0, 10);
964
- }
965
- throw new Error('watcher sem prontidao confirmada');
1014
+ catch {
1015
+ return 'invalido';
1016
+ }
1017
+ return ready && ready.token === token && ready.sessionId === sessionId && ready.despachoEm === sessao.despachadaEm &&
1018
+ JSON.stringify(ready.identidade) === JSON.stringify(identidade) && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo'
1019
+ ? 'confirmado' : 'invalido';
1020
+ }, identidade);
1021
+ (0, ledger_1.registrar)(dir, thread.id, 'session_watcher_started', { fase: sessao.fase, sessionId,
1022
+ despachoEm: sessao.despachadaEm, pid: identidade.pid, identidade, readyPath, token });
1023
+ (0, codex_runner_1.gravarAtomico)(ackPath, { token });
1024
+ return identidade.pid;
966
1025
  }
967
1026
  catch (e) {
968
1027
  falha();
969
- // Encerrar apenas o filho cuja identidade continua sendo a capturada no spawn.
970
- if (identidade && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo')
1028
+ // Lentidao dentro do teto nao encerra o filho; so teto ou prontidao invalida autorizam o sinal.
1029
+ if (e instanceof ErroDoApertoWatcher && e.encerrarOutro && identidade && (0, codex_runner_1.estadoProcesso)(identidade) === 'vivo')
971
1030
  child.kill('SIGTERM');
972
1031
  throw e;
973
1032
  }
@@ -978,18 +1037,30 @@ async function acompanhar() {
978
1037
  try {
979
1038
  const readyPath = process.argv[5], token = process.argv[6];
980
1039
  if (readyPath && token) {
1040
+ const identidadePai = (0, codex_runner_1.identidadeProcesso)(process.ppid);
1041
+ if (!identidadePai)
1042
+ throw new Error('watcher sem confirmacao do pai');
981
1043
  validarRegistroWatcher(carregado.raiz, sessionId);
982
1044
  const sessao = (0, session_events_1.exigirSessaoDespachada)((0, session_events_1.resolverSessao)(carregado.raiz, sessionId, threadId).sessao, sessionId);
983
1045
  const identidade = (0, codex_runner_1.identidadeProcesso)(process.pid);
984
1046
  if (!identidade)
985
1047
  throw new Error('watcher sem identidade propria');
986
1048
  (0, codex_runner_1.gravarAtomico)(readyPath, { token, sessionId, despachoEm: sessao.despachadaEm, identidade });
987
- const deadline = Date.now() + 5000;
988
- while (lerJson(readyPath + '.ack')?.token !== token) {
989
- if (Date.now() >= deadline)
990
- throw new Error('watcher sem confirmacao do pai');
991
- await new Promise(r => setTimeout(r, 10));
992
- }
1049
+ await esperarConfirmacaoDoPaiWatcher(() => {
1050
+ if (!(0, ledger_1.threadPresente)((0, thread_1.dirThread)(carregado.raiz, threadId)))
1051
+ return 'invalido';
1052
+ const ackPath = readyPath + '.ack';
1053
+ if (!fs.existsSync(ackPath))
1054
+ return 'pendente';
1055
+ let ack;
1056
+ try {
1057
+ ack = lerJson(ackPath);
1058
+ }
1059
+ catch {
1060
+ return 'invalido';
1061
+ }
1062
+ return ack?.token === token ? 'confirmado' : 'invalido';
1063
+ }, identidadePai);
993
1064
  }
994
1065
  await acompanharSessao(carregado, sessionId, { threadId });
995
1066
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkastery/cli",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
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",