@orkastery/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +163 -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-objective-message.sh +17 -0
- package/adapters/hermes/bin/ork-objective-status.sh +12 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +26 -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 +135 -26
- 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 +458 -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 +11 -8
- package/dist/auditrun.js +5 -4
- package/dist/board.js +173 -22
- 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 +216 -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 +183 -24
- 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 +21 -0
- package/dist/gates.js +55 -9
- 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 +1736 -93
- 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 +107 -2
- package/dist/licoes.js +215 -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 +596 -150
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -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 +637 -115
- 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 +683 -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 +150 -15
- 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 +2 -0
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +35 -1
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +2 -2
- 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 +25 -4
- 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/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/scope-check-capability-map/SKILL.md +17 -7
|
@@ -0,0 +1,163 @@
|
|
|
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.CADENCIA_PADRAO = exports.CADENCIAS = exports.CONTRATO_CADENCIA = void 0;
|
|
37
|
+
exports.extrairTagDoPulse = extrairTagDoPulse;
|
|
38
|
+
exports.lerCadencia = lerCadencia;
|
|
39
|
+
exports.gravarCadencia = gravarCadencia;
|
|
40
|
+
exports.janelaDoInstante = janelaDoInstante;
|
|
41
|
+
exports.inicioDaProximaJanela = inicioDaProximaJanela;
|
|
42
|
+
exports.lerUltimoResumo = lerUltimoResumo;
|
|
43
|
+
exports.gravarUltimoResumo = gravarUltimoResumo;
|
|
44
|
+
exports.janelaAberta = janelaAberta;
|
|
45
|
+
exports.textoDaCadencia = textoDaCadencia;
|
|
46
|
+
/**
|
|
47
|
+
* I-50 (RM-039): a cadencia do resumo do pulse, trocada pelo dono em conversa com uma tag.
|
|
48
|
+
*
|
|
49
|
+
* As tags sao as que o dono definiu:
|
|
50
|
+
* `#OrkPulseOff` de 8 em 8 horas, ancorado as 08h do fuso dono (08h, 16h e 00h);
|
|
51
|
+
* `#OrkPulseOn` de 2 em 2 horas;
|
|
52
|
+
* `#OrkPulseOn-15m`, `-30m` e `-60m`: de 15, 30 ou 60 minutos.
|
|
53
|
+
*
|
|
54
|
+
* O cron bate em intervalo curto e quem decide se ESTA batida entrega e o nucleo, lendo a
|
|
55
|
+
* cadencia duravel em `.orkastery/monitor/pulse-cadencia.json`. Assim a troca de tag vale na
|
|
56
|
+
* proxima batida, sem editar o crontab. A cadencia governa o status periodico; pergunta de
|
|
57
|
+
* verdade ao dono nao espera janela nenhuma e sai na hora.
|
|
58
|
+
*
|
|
59
|
+
* Cada cadencia divide o dia do dono em janelas ancoradas (15m em :00, :15, :30 e :45; 2h nas
|
|
60
|
+
* horas pares; 8h as 08h, 16h e 00h). Entrega-se no maximo uma vez por janela.
|
|
61
|
+
*/
|
|
62
|
+
const fs = __importStar(require("node:fs"));
|
|
63
|
+
const path = __importStar(require("node:path"));
|
|
64
|
+
const horario_1 = require("./horario");
|
|
65
|
+
const estado_thread_1 = require("./estado-thread");
|
|
66
|
+
exports.CONTRATO_CADENCIA = 'ork.pulse-cadencia/v1';
|
|
67
|
+
const ARQUIVO_CADENCIA = 'pulse-cadencia.json';
|
|
68
|
+
const ARQUIVO_ULTIMO_RESUMO = 'pulse-ultimo-resumo.json';
|
|
69
|
+
exports.CADENCIAS = {
|
|
70
|
+
'#OrkPulseOff': { tag: '#OrkPulseOff', janelaMin: 480, ancoraMin: 8 * 60, descricao: 'de 8 em 8 horas, as 08h, 16h e 00h' },
|
|
71
|
+
'#OrkPulseOn': { tag: '#OrkPulseOn', janelaMin: 120, ancoraMin: 0, descricao: 'de 2 em 2 horas' },
|
|
72
|
+
'#OrkPulseOn-15m': { tag: '#OrkPulseOn-15m', janelaMin: 15, ancoraMin: 0, descricao: 'de 15 em 15 minutos' },
|
|
73
|
+
'#OrkPulseOn-30m': { tag: '#OrkPulseOn-30m', janelaMin: 30, ancoraMin: 0, descricao: 'de 30 em 30 minutos' },
|
|
74
|
+
'#OrkPulseOn-60m': { tag: '#OrkPulseOn-60m', janelaMin: 60, ancoraMin: 0, descricao: 'de hora em hora' },
|
|
75
|
+
};
|
|
76
|
+
/** Sem tag gravada, vale a hora cheia: o pedido do dono em 20/09/2026. */
|
|
77
|
+
exports.CADENCIA_PADRAO = '#OrkPulseOn-60m';
|
|
78
|
+
/** A primeira tag de pulse do texto, em qualquer canal; `null` quando nao ha. */
|
|
79
|
+
function extrairTagDoPulse(texto) {
|
|
80
|
+
if (typeof texto !== 'string')
|
|
81
|
+
return null;
|
|
82
|
+
const m = /#OrkPulse(On|Off)(?:-(15|30|60)m)?(?![\w-])/i.exec(texto);
|
|
83
|
+
if (!m)
|
|
84
|
+
return null;
|
|
85
|
+
if (m[1].toLowerCase() === 'off')
|
|
86
|
+
return m[2] ? null : '#OrkPulseOff';
|
|
87
|
+
return m[2] ? `#OrkPulseOn-${m[2]}m` : '#OrkPulseOn';
|
|
88
|
+
}
|
|
89
|
+
const dirDoMonitor = (raiz, estadoDir) => estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor');
|
|
90
|
+
/** A cadencia em vigor: a gravada pelo dono, ou a padrao. Arquivo ilegivel vale a padrao. */
|
|
91
|
+
function lerCadencia(raiz, estadoDir) {
|
|
92
|
+
try {
|
|
93
|
+
const bruto = JSON.parse(fs.readFileSync(path.join(dirDoMonitor(raiz, estadoDir), ARQUIVO_CADENCIA), 'utf8'));
|
|
94
|
+
if (bruto.contrato === exports.CONTRATO_CADENCIA && Object.hasOwn(exports.CADENCIAS, bruto.tag))
|
|
95
|
+
return { ...exports.CADENCIAS[bruto.tag], gravada: bruto };
|
|
96
|
+
}
|
|
97
|
+
catch { /* sem cadencia gravada: vale a padrao */ }
|
|
98
|
+
return { ...exports.CADENCIAS[exports.CADENCIA_PADRAO], gravada: null };
|
|
99
|
+
}
|
|
100
|
+
/** Grava a tag do dono. `por` e quem mandou (o remetente autenticado do canal, ou o operador). */
|
|
101
|
+
function gravarCadencia(raiz, tag, origem, estadoDir) {
|
|
102
|
+
if (!Object.hasOwn(exports.CADENCIAS, tag))
|
|
103
|
+
throw new Error(`pulse.cadencia: tag desconhecida ${tag}`);
|
|
104
|
+
const por = origem.por.trim();
|
|
105
|
+
if (!por)
|
|
106
|
+
throw new Error('pulse.cadencia: informe quem trocou a cadencia');
|
|
107
|
+
const gravada = { contrato: exports.CONTRATO_CADENCIA, tag, por, canal: origem.canal, em: origem.em ?? new Date().toISOString() };
|
|
108
|
+
const dir = dirDoMonitor(raiz, estadoDir);
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
+
const temp = path.join(dir, `.${ARQUIVO_CADENCIA}.${process.pid}.tmp`);
|
|
111
|
+
fs.writeFileSync(temp, JSON.stringify(gravada, null, 2) + '\n', { mode: 0o600 });
|
|
112
|
+
fs.renameSync(temp, path.join(dir, ARQUIVO_CADENCIA));
|
|
113
|
+
return gravada;
|
|
114
|
+
}
|
|
115
|
+
/** O numero da janela de um instante: minutos locais desde a ancora, divididos pela janela. */
|
|
116
|
+
function janelaDoInstante(def, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
|
|
117
|
+
const p = (0, horario_1.partesLocais)(quando, fuso);
|
|
118
|
+
const localMin = Date.UTC(Number(p.ano), Number(p.mes) - 1, Number(p.dia), Number(p.hora), Number(p.minuto)) / 60000;
|
|
119
|
+
return Math.floor((localMin - def.ancoraMin) / def.janelaMin);
|
|
120
|
+
}
|
|
121
|
+
/** Quando a proxima janela comeca, em ISO: para dizer ao dono a hora do proximo resumo. */
|
|
122
|
+
function inicioDaProximaJanela(def, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
|
|
123
|
+
const atual = janelaDoInstante(def, quando, fuso);
|
|
124
|
+
let t = Date.parse(quando);
|
|
125
|
+
// Avanca minuto a minuto no maximo uma janela: simples e sem aritmetica de fuso.
|
|
126
|
+
for (let i = 0; i <= def.janelaMin; i++) {
|
|
127
|
+
t += 60_000;
|
|
128
|
+
if (janelaDoInstante(def, t, fuso) > atual) {
|
|
129
|
+
const d = new Date(t);
|
|
130
|
+
d.setUTCSeconds(0, 0);
|
|
131
|
+
return d.toISOString();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return new Date(t).toISOString();
|
|
135
|
+
}
|
|
136
|
+
function lerUltimoResumo(raiz, estadoDir) {
|
|
137
|
+
try {
|
|
138
|
+
const bruto = JSON.parse(fs.readFileSync(path.join(dirDoMonitor(raiz, estadoDir), ARQUIVO_ULTIMO_RESUMO), 'utf8'));
|
|
139
|
+
return typeof bruto.em === 'string' && Number.isFinite(Date.parse(bruto.em)) ? bruto.em : null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function gravarUltimoResumo(raiz, em, estadoDir) {
|
|
146
|
+
const dir = dirDoMonitor(raiz, estadoDir);
|
|
147
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
148
|
+
fs.writeFileSync(path.join(dir, ARQUIVO_ULTIMO_RESUMO), JSON.stringify({ em }) + '\n', { mode: 0o600 });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Esta batida do cron pode entregar o status periodico? So quando a janela mudou desde o ultimo
|
|
152
|
+
* resumo entregue. Pergunta nova ao dono nao passa por aqui: ela sai na hora.
|
|
153
|
+
*/
|
|
154
|
+
function janelaAberta(def, ultimoResumoEm, quando, fuso = (0, horario_1.fusoDoDono)().fuso) {
|
|
155
|
+
if (!ultimoResumoEm)
|
|
156
|
+
return true;
|
|
157
|
+
return janelaDoInstante(def, quando, fuso) > janelaDoInstante(def, ultimoResumoEm, fuso);
|
|
158
|
+
}
|
|
159
|
+
/** A confirmacao que volta ao dono pelo mesmo canal, no fuso dele. */
|
|
160
|
+
function textoDaCadencia(def, quando) {
|
|
161
|
+
return `Pulse ${def.descricao} (${def.tag}). Pergunta para você continua saindo na hora. ` +
|
|
162
|
+
`Próximo resumo com novidade a partir de ${(0, horario_1.formatarDataHoraRotulada)(inicioDaProximaJanela(def, quando))}.`;
|
|
163
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
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.PRAZO_PADRAO_MIN = exports.ENDERECO_DA_RESPOSTA = exports.ALVO_DO_PULSE = exports.TAMANHO_DO_CODIGO = exports.ALFABETO_DO_CODIGO = exports.CONTRATO_CONSENTIMENTO = void 0;
|
|
37
|
+
exports.arquivoDoConsentimento = arquivoDoConsentimento;
|
|
38
|
+
exports.gerarCodigo = gerarCodigo;
|
|
39
|
+
exports.interpretarResposta = interpretarResposta;
|
|
40
|
+
exports.assinaturaDosCandidatos = assinaturaDosCandidatos;
|
|
41
|
+
exports.lerConsentimento = lerConsentimento;
|
|
42
|
+
exports.consentimentoVigente = consentimentoVigente;
|
|
43
|
+
exports.abrirConsentimento = abrirConsentimento;
|
|
44
|
+
exports.responderConsentimento = responderConsentimento;
|
|
45
|
+
exports.marcarLoteEntregue = marcarLoteEntregue;
|
|
46
|
+
/**
|
|
47
|
+
* I-41 (D16): o consentimento que separa a camada 1 da camada 2.
|
|
48
|
+
*
|
|
49
|
+
* O resumo termina perguntando "Posso te mandar as perguntas agora?". Essa pergunta e um pedido
|
|
50
|
+
* HITL como qualquer outro, e obedece as mesmas regras: resposta por letra ou palavra curta, sem
|
|
51
|
+
* identificador longo na mao do humano, e proveniencia provada pelo HMAC do ingresso autenticado.
|
|
52
|
+
*
|
|
53
|
+
* O QUE NAO MUDA, e o motivo: `assinaturaDaResposta` e `autenticarResposta` sao chamadas daqui
|
|
54
|
+
* sem uma linha alterada, e o corpo assinado continua o de sempre. E essa assinatura que impede o
|
|
55
|
+
* agente consentir no lugar do dono, e ela nao se negocia.
|
|
56
|
+
*
|
|
57
|
+
* I-41 (GO-FIX 1, B1): O ENDERECO. O dono digita "P4EJ a" no Telegram, e o gateway que recebe essa
|
|
58
|
+
* mensagem nao conhece identificador de pedido nenhum. Por isso o ingresso assina o endereco do
|
|
59
|
+
* pulse (`ALVO_DO_PULSE` na posicao de thread, `ENDERECO_DA_RESPOSTA` na posicao de pedido) junto
|
|
60
|
+
* com o texto exato que o dono digitou. O codigo esta DENTRO do texto assinado, entao um "sim" a um
|
|
61
|
+
* resumo nao vale para outro: o codigo precisa bater com o pedido aberto, e a janela de 60
|
|
62
|
+
* segundos do ingresso fecha a repeticao. Quem traduz o codigo no pedido e o nucleo.
|
|
63
|
+
*
|
|
64
|
+
* O consentimento nao e de uma thread: ele e do projeto, porque o resumo atravessa todas. O alvo e
|
|
65
|
+
* uma constante e nao um nome de thread: ids de thread deste projeto comecam com o abbrev.
|
|
66
|
+
*
|
|
67
|
+
* Silencio nao perde nada. Consentimento vencido nao nega e nao apaga: o proximo resumo abre
|
|
68
|
+
* outro e cita as perguntas guardadas. Os itens vivem no pulse, nao aqui.
|
|
69
|
+
*/
|
|
70
|
+
const fs = __importStar(require("node:fs"));
|
|
71
|
+
const path = __importStar(require("node:path"));
|
|
72
|
+
const node_crypto_1 = require("node:crypto");
|
|
73
|
+
const estado_thread_1 = require("./estado-thread");
|
|
74
|
+
const hitl_gates_1 = require("./hitl-gates");
|
|
75
|
+
exports.CONTRATO_CONSENTIMENTO = 'ork.pulse-consent/v1';
|
|
76
|
+
/**
|
|
77
|
+
* Crockford base32 sem os quatro ambiguos (I, L, O, U). Com letra na frente e ao menos um digito,
|
|
78
|
+
* quatro caracteres ainda dao mais de 350.000 codigos, e o escopo de unicidade que importa e um: o
|
|
79
|
+
* consentimento aberto agora. Ambiguo fora do alfabeto importa porque o dono digita isto no
|
|
80
|
+
* celular, onde 0 e O sao a mesma coisa.
|
|
81
|
+
*/
|
|
82
|
+
exports.ALFABETO_DO_CODIGO = '23456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
83
|
+
exports.TAMANHO_DO_CODIGO = 4;
|
|
84
|
+
const DIGITOS_DO_CODIGO = '23456789';
|
|
85
|
+
const LETRAS_DO_CODIGO = 'ABCDEFGHJKMNPQRSTVWXYZ';
|
|
86
|
+
/**
|
|
87
|
+
* I-41 (GO-FIX 1, B1): o endereco do pulse no corpo assinado.
|
|
88
|
+
*
|
|
89
|
+
* O ingresso poe `ALVO_DO_PULSE` na posicao de thread e `ENDERECO_DA_RESPOSTA` na posicao de
|
|
90
|
+
* pedido, e o texto que o dono digitou na posicao de resposta. Nenhum dos dois e nome de thread
|
|
91
|
+
* ou de pedido: um envelope assinado para uma thread de verdade nao serve aqui, e vice-versa.
|
|
92
|
+
*/
|
|
93
|
+
exports.ALVO_DO_PULSE = 'pulse';
|
|
94
|
+
exports.ENDERECO_DA_RESPOSTA = 'resposta';
|
|
95
|
+
/** Uma hora, a mesma janela do resumo pedido em 20/09. Vencido nao nega: so deixa de valer. */
|
|
96
|
+
exports.PRAZO_PADRAO_MIN = 60;
|
|
97
|
+
const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
98
|
+
function arquivoDoConsentimento(raiz, estadoDir) {
|
|
99
|
+
return path.join(estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor'), 'pulse-consentimento.json');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Codigo novo, uniforme, diferente dos que estiverem em uso agora.
|
|
103
|
+
*
|
|
104
|
+
* I-41 (GO-FIX 1): todo codigo comeca por LETRA e tem pelo menos um DIGITO. O ingresso do Telegram
|
|
105
|
+
* reconhece a resposta ao resumo pela forma "codigo + resposta". Com digito, palavra comum de
|
|
106
|
+
* quatro letras ("HMMM ok") nunca tem essa forma, e a conversa com o assistente continua indo para
|
|
107
|
+
* o assistente. Com letra na frente, o codigo nunca e lido como resposta ao lote, que comeca pelo
|
|
108
|
+
* numero da pergunta: "22A2 a" seria "22a 2a".
|
|
109
|
+
*/
|
|
110
|
+
function gerarCodigo(emUso = []) {
|
|
111
|
+
const usados = new Set(emUso.map(c => c.toUpperCase()));
|
|
112
|
+
for (let tentativa = 0; tentativa < 256; tentativa++) {
|
|
113
|
+
let codigo = LETRAS_DO_CODIGO[(0, node_crypto_1.randomInt)(LETRAS_DO_CODIGO.length)];
|
|
114
|
+
for (let i = 1; i < exports.TAMANHO_DO_CODIGO; i++)
|
|
115
|
+
codigo += exports.ALFABETO_DO_CODIGO[(0, node_crypto_1.randomInt)(exports.ALFABETO_DO_CODIGO.length)];
|
|
116
|
+
if (![...codigo].some(c => DIGITOS_DO_CODIGO.includes(c)))
|
|
117
|
+
continue;
|
|
118
|
+
if (!usados.has(codigo))
|
|
119
|
+
return codigo;
|
|
120
|
+
}
|
|
121
|
+
// 810.000 combinacoes contra um punhado em uso: chegar aqui e defeito, nao azar.
|
|
122
|
+
throw new Error('consentimento do pulse: não foi possível gerar código curto livre');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* O que o dono pode digitar. Letra `a`/`b`, ou palavra curta, com ou sem o codigo na frente,
|
|
126
|
+
* em qualquer caixa. O que ele NAO precisa digitar e identificador longo.
|
|
127
|
+
*
|
|
128
|
+
* Quando o codigo vem escrito, ele precisa bater: um "sim" digitado para o resumo anterior nao
|
|
129
|
+
* consente com o resumo de agora. Quando nao vem, o pedido aberto e um so e a letra basta.
|
|
130
|
+
*/
|
|
131
|
+
function interpretarResposta(bruto, codigo) {
|
|
132
|
+
if (typeof bruto !== 'string' || bruto.length > 64)
|
|
133
|
+
return undefined;
|
|
134
|
+
const limpo = bruto.trim().toLowerCase().replace(/[.,!]+$/, '');
|
|
135
|
+
const prefixo = codigo.toLowerCase();
|
|
136
|
+
const corpo = limpo.startsWith(prefixo) ? limpo.slice(prefixo.length).trim() : limpo;
|
|
137
|
+
if (limpo !== corpo && corpo === '')
|
|
138
|
+
return undefined; // só o código, sem escolha, não é resposta
|
|
139
|
+
if (['a', 's', 'sim', 'pode', 'manda'].includes(corpo))
|
|
140
|
+
return 'sim';
|
|
141
|
+
if (['b', 'n', 'nao', 'não', 'agora nao', 'agora não'].includes(corpo))
|
|
142
|
+
return 'nao';
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A assinatura de um conjunto de candidatos: os mesmos gates, a mesma oferta. O pedido aberto nao
|
|
147
|
+
* entra: ele vence e renasce sem que a pergunta mude, e vencer nao e noticia para o dono.
|
|
148
|
+
*/
|
|
149
|
+
function assinaturaDosCandidatos(candidatos) {
|
|
150
|
+
return sha(JSON.stringify(candidatos.map(c => [c.thread, c.fase, c.motivo, c.fonte ?? 'gate'])));
|
|
151
|
+
}
|
|
152
|
+
function lerConsentimento(raiz, estadoDir) {
|
|
153
|
+
const arquivo = arquivoDoConsentimento(raiz, estadoDir);
|
|
154
|
+
if (!fs.existsSync(arquivo))
|
|
155
|
+
return undefined;
|
|
156
|
+
const salvo = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
|
|
157
|
+
if (salvo.versao !== 1 || salvo.pedido?.contrato !== exports.CONTRATO_CONSENTIMENTO) {
|
|
158
|
+
throw new Error('consentimento do pulse: estado inválido; exige inspeção');
|
|
159
|
+
}
|
|
160
|
+
// Estado gravado antes do GO-FIX 1 nao guardava candidatos: sem eles, o sim nao tem o que servir.
|
|
161
|
+
if (!Array.isArray(salvo.pedido.candidatos)) {
|
|
162
|
+
salvo.pedido = { ...salvo.pedido, candidatos: [], candidatosSha256: assinaturaDosCandidatos([]) };
|
|
163
|
+
}
|
|
164
|
+
return salvo;
|
|
165
|
+
}
|
|
166
|
+
function gravar(raiz, estado, estadoDir) {
|
|
167
|
+
const arquivo = arquivoDoConsentimento(raiz, estadoDir);
|
|
168
|
+
fs.mkdirSync(path.dirname(arquivo), { recursive: true });
|
|
169
|
+
const tmp = arquivo + '.tmp';
|
|
170
|
+
fs.writeFileSync(tmp, JSON.stringify(estado) + '\n', { mode: 0o600 });
|
|
171
|
+
fs.renameSync(tmp, arquivo);
|
|
172
|
+
}
|
|
173
|
+
/** O pedido que ainda vale agora: aberto, dentro do prazo e sem resposta gravada. */
|
|
174
|
+
function consentimentoVigente(raiz, quando, estadoDir) {
|
|
175
|
+
const estado = lerConsentimento(raiz, estadoDir);
|
|
176
|
+
if (!estado || estado.respondido)
|
|
177
|
+
return undefined;
|
|
178
|
+
return Date.parse(quando) < Date.parse(estado.pedido.prazo) ? estado.pedido : undefined;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Abre o pedido que acompanha um resumo. Um pedido vigente NAO e substituido: trocar o codigo
|
|
182
|
+
* debaixo de quem esta digitando e o defeito que fez a mesma pergunta sair com tres
|
|
183
|
+
* identificadores diferentes entre 16/09 e 20/09.
|
|
184
|
+
*/
|
|
185
|
+
function abrirConsentimento(raiz, entrada) {
|
|
186
|
+
const candidatos = [...(entrada.candidatos ?? [])];
|
|
187
|
+
const candidatosSha256 = assinaturaDosCandidatos(candidatos);
|
|
188
|
+
const vigente = consentimentoVigente(raiz, entrada.quando, entrada.estadoDir);
|
|
189
|
+
if (vigente && vigente.resumoSha256 === entrada.resumoSha256 && vigente.candidatosSha256 === candidatosSha256)
|
|
190
|
+
return vigente;
|
|
191
|
+
const anterior = lerConsentimento(raiz, entrada.estadoDir)?.pedido.codigo;
|
|
192
|
+
const prazoMin = entrada.prazoMin ?? exports.PRAZO_PADRAO_MIN;
|
|
193
|
+
const pedido = {
|
|
194
|
+
contrato: exports.CONTRATO_CONSENTIMENTO, id: (0, node_crypto_1.randomUUID)(),
|
|
195
|
+
// O codigo novo nunca repete o anterior: um "sim" atrasado ao resumo velho nao vale para o novo.
|
|
196
|
+
codigo: gerarCodigo(anterior ? [anterior] : []),
|
|
197
|
+
criadoEm: entrada.quando,
|
|
198
|
+
prazo: new Date(Date.parse(entrada.quando) + prazoMin * 60000).toISOString(),
|
|
199
|
+
acumuladas: candidatos.length, resumoSha256: entrada.resumoSha256, candidatos, candidatosSha256,
|
|
200
|
+
};
|
|
201
|
+
gravar(raiz, { versao: 1, pedido }, entrada.estadoDir);
|
|
202
|
+
return pedido;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Recebe a resposta do dono pelo codigo curto.
|
|
206
|
+
*
|
|
207
|
+
* A ordem importa e e a mesma do gate: primeiro proveniencia, depois conteudo. Um envelope sem
|
|
208
|
+
* assinatura valida nunca chega a ser interpretado como "sim", e por isso texto malformado de
|
|
209
|
+
* origem nao autenticada nao consegue liberar lote nenhum. O envelope e o que o ingresso assinou
|
|
210
|
+
* para o endereco do pulse, com o texto que o dono digitou ("P4EJ a").
|
|
211
|
+
*
|
|
212
|
+
* Repetir a MESMA resposta devolve o que ja foi decidido, inclusive por outra mensagem: o dono que
|
|
213
|
+
* nao viu o lote chegar manda "P4EJ a" de novo e recebe o mesmo lote, nunca um lote novo. Mudar
|
|
214
|
+
* de ideia sobre o mesmo resumo e recusado com o que ja vale.
|
|
215
|
+
*/
|
|
216
|
+
function responderConsentimento(raiz, entrada) {
|
|
217
|
+
const quando = entrada.quando ?? new Date().toISOString();
|
|
218
|
+
const estado = lerConsentimento(raiz, entrada.estadoDir);
|
|
219
|
+
if (!estado)
|
|
220
|
+
throw new Error('consentimento do pulse: não há pedido aberto');
|
|
221
|
+
const { pedido } = estado;
|
|
222
|
+
if (pedido.codigo.toUpperCase() !== String(entrada.codigo).trim().toUpperCase()) {
|
|
223
|
+
throw new Error('consentimento do pulse: código não confere com o pedido aberto');
|
|
224
|
+
}
|
|
225
|
+
// Proveniencia primeiro, pelo caminho de sempre, no endereco do pulse.
|
|
226
|
+
(0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_PULSE, exports.ENDERECO_DA_RESPOSTA, entrada.envelope, entrada.envelope.recebidoEm);
|
|
227
|
+
(0, hitl_gates_1.autenticarResposta)(exports.ALVO_DO_PULSE, exports.ENDERECO_DA_RESPOSTA, entrada.envelope, quando);
|
|
228
|
+
const resposta = interpretarResposta(entrada.envelope.resposta, pedido.codigo);
|
|
229
|
+
const recibo = sha(entrada.envelope.prova);
|
|
230
|
+
if (estado.respondido) {
|
|
231
|
+
// Uso unico por significado: a mesma resposta do mesmo dono devolve o que ja foi decidido.
|
|
232
|
+
if (resposta !== estado.respondido.resposta || estado.respondido.por !== entrada.envelope.por) {
|
|
233
|
+
throw new Error('consentimento do pulse: já respondido; resposta divergente recusada');
|
|
234
|
+
}
|
|
235
|
+
return { resposta: estado.respondido.resposta, repetida: true, pedido, respondido: estado.respondido };
|
|
236
|
+
}
|
|
237
|
+
if (Date.parse(quando) >= Date.parse(pedido.prazo)) {
|
|
238
|
+
throw new Error('consentimento do pulse: pedido vencido; nenhuma autorização concedida');
|
|
239
|
+
}
|
|
240
|
+
if (!resposta)
|
|
241
|
+
throw new Error('consentimento do pulse: responda com a (sim) ou b (agora não)');
|
|
242
|
+
const respondido = {
|
|
243
|
+
resposta, respondidoEm: quando, mensagem: entrada.envelope.mensagem, por: entrada.envelope.por, recibo,
|
|
244
|
+
};
|
|
245
|
+
gravar(raiz, { ...estado, respondido }, entrada.estadoDir);
|
|
246
|
+
return { resposta, repetida: false, pedido, respondido };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Marca o lote consentido como entregue, com os numeros que ele levou. Sem isto, um sim viraria
|
|
250
|
+
* autorizacao permanente e a cada resposta sairia um lote novo, que e a inundacao voltando por
|
|
251
|
+
* outra porta. Com os numeros, repetir o sim devolve o MESMO lote.
|
|
252
|
+
*/
|
|
253
|
+
function marcarLoteEntregue(raiz, quando, estadoDir, numeros) {
|
|
254
|
+
const estado = lerConsentimento(raiz, estadoDir);
|
|
255
|
+
if (!estado?.respondido || estado.respondido.resposta !== 'sim')
|
|
256
|
+
throw new Error('consentimento do pulse: não há sim para servir');
|
|
257
|
+
if (estado.respondido.loteEntregueEm)
|
|
258
|
+
throw new Error('consentimento do pulse: este sim já serviu um lote');
|
|
259
|
+
gravar(raiz, { ...estado, respondido: { ...estado.respondido, loteEntregueEm: quando, numeros: [...numeros] } }, estadoDir);
|
|
260
|
+
}
|