@orkastery/cli 0.2.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/check.md +4 -3
- package/adapters/claude-code/commands/goal.md +3 -2
- package/adapters/claude-code/commands/master.md +3 -2
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-guard.js +1 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +161 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +24 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +136 -27
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +460 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +12 -9
- package/dist/auditrun.js +5 -4
- package/dist/board.js +174 -23
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +223 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +184 -25
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +22 -1
- package/dist/gates.js +59 -11
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1741 -94
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +109 -2
- package/dist/licoes.js +221 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +612 -151
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/policies.js +72 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +640 -118
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +700 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +168 -19
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +5 -1
- package/eval/casos/master-metrics.json +1 -1
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +37 -3
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +3 -3
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +28 -7
- package/references/definition-of-done.md +2 -2
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/README.md +3 -3
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/decision-triage/SKILL.md +4 -4
- package/skills/governance/narrative-guardian/SKILL.md +1 -1
- package/skills/governance/roadmap-keeper/SKILL.md +1 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
- package/skills/observability/thread-tracing/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +2 -2
- package/skills/phases/goal-definition/SKILL.md +1 -1
- package/skills/phases/master-metrics/SKILL.md +9 -7
- package/skills/phases/plan-specification/SKILL.md +1 -1
- package/skills/phases/ship-release/SKILL.md +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
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.ENV_HITL_VERIFIERS = void 0;
|
|
37
|
+
exports.publicHitlVerifiers = publicHitlVerifiers;
|
|
38
|
+
exports.publicReceiptAuthority = publicReceiptAuthority;
|
|
39
|
+
exports.publicBindingMatches = publicBindingMatches;
|
|
40
|
+
exports.signPublicReceipt = signPublicReceipt;
|
|
41
|
+
exports.verifyPublicReceipt = verifyPublicReceipt;
|
|
42
|
+
/** Prova pública de recibos: a chave de ingresso permanece no host confiável.
|
|
43
|
+
* A raiz de confiança vem do ambiente de startup, nunca do recibo ou de toolargs.
|
|
44
|
+
* O filho pode verificar provas, mas não emitir uma para outros bytes/identidades.
|
|
45
|
+
*/
|
|
46
|
+
const node_crypto_1 = require("node:crypto");
|
|
47
|
+
const fs = __importStar(require("node:fs"));
|
|
48
|
+
const path = __importStar(require("node:path"));
|
|
49
|
+
const zod_1 = require("zod");
|
|
50
|
+
const hitl_native_1 = require("./hitl-native");
|
|
51
|
+
exports.ENV_HITL_VERIFIERS = 'ORK_RECEIPT_VERIFIERS';
|
|
52
|
+
const slots = ['telegram:legacy', 'telegram:hermes', 'telegram:openclaw', 'native:hermes', 'native:openclaw'];
|
|
53
|
+
const sha = (v) => (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(v)).digest('hex');
|
|
54
|
+
const verifierSchema = zod_1.z.object({ publicKey: zod_1.z.string().max(128), bindingSha256: zod_1.z.string().regex(/^[a-f0-9]{64}$/) }).strict();
|
|
55
|
+
const bundleSchema = zod_1.z.object({ schema: zod_1.z.literal('ork.hitl-verifiers/v1'),
|
|
56
|
+
verifiers: zod_1.z.record(zod_1.z.enum(slots), verifierSchema) }).strict();
|
|
57
|
+
const slotFor = (canal, native) => {
|
|
58
|
+
const slot = `${native ? 'native' : 'telegram'}:${canal ?? 'legacy'}`;
|
|
59
|
+
if (!slots.includes(slot))
|
|
60
|
+
throw Error('hitl.receipt.authority');
|
|
61
|
+
return slot;
|
|
62
|
+
};
|
|
63
|
+
function material(slot, env) {
|
|
64
|
+
if (slot.startsWith('native:')) {
|
|
65
|
+
const host = slot.slice(7);
|
|
66
|
+
return { key: (0, hitl_native_1.nativeKey)(host, env), binding: (0, hitl_native_1.nativeBinding)(host, env) };
|
|
67
|
+
}
|
|
68
|
+
const host = slot.slice(9), suffix = host === 'legacy' ? '' : `_${host.toUpperCase()}`;
|
|
69
|
+
const key = env[`ORK_HITL_INGRESS_KEY${suffix}`] ?? '';
|
|
70
|
+
if (Buffer.byteLength(key) < 32 || (host !== 'legacy' && key === env[`ORK_HITL_INGRESS_KEY_${host === 'hermes' ? 'OPENCLAW' : 'HERMES'}`]))
|
|
71
|
+
throw Error('hitl.receipt.credentials');
|
|
72
|
+
const binding = host === 'openclaw' ? env.ORK_HITL_OPENCLAW_ACCOUNT?.trim() : null;
|
|
73
|
+
if (host === 'openclaw' && !binding)
|
|
74
|
+
throw Error('hitl.receipt.credentials');
|
|
75
|
+
return { key, binding };
|
|
76
|
+
}
|
|
77
|
+
function signingKey(key, slot) {
|
|
78
|
+
const seed = (0, node_crypto_1.createHmac)('sha256', key).update(`ork.hitl-public-receipt/v1:${slot}`).digest();
|
|
79
|
+
return (0, node_crypto_1.createPrivateKey)({ key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]),
|
|
80
|
+
format: 'der', type: 'pkcs8' });
|
|
81
|
+
}
|
|
82
|
+
function inherited(env) {
|
|
83
|
+
const raw = env[exports.ENV_HITL_VERIFIERS];
|
|
84
|
+
if (!raw)
|
|
85
|
+
return {};
|
|
86
|
+
if (Buffer.byteLength(raw) > 4096)
|
|
87
|
+
throw Error('hitl.receipt.verifiers-invalid');
|
|
88
|
+
return bundleSchema.parse(JSON.parse(raw)).verifiers;
|
|
89
|
+
}
|
|
90
|
+
/** Somente material público; é seguro transportar em config/env do filho. */
|
|
91
|
+
function publicHitlVerifiers(env = process.env) {
|
|
92
|
+
const verifiers = inherited(env);
|
|
93
|
+
for (const slot of slots) {
|
|
94
|
+
const host = slot.split(':')[1];
|
|
95
|
+
const name = slot.startsWith('native:') ? `ORK_HITL_NATIVE_KEY_${host.toUpperCase()}` :
|
|
96
|
+
`ORK_HITL_INGRESS_KEY${host === 'legacy' ? '' : `_${host.toUpperCase()}`}`;
|
|
97
|
+
// Configuração privada presente prevalece: chave inválida/rotacionada não cai
|
|
98
|
+
// silenciosamente num verificador público herdado da configuração anterior.
|
|
99
|
+
if (env[name] !== undefined) {
|
|
100
|
+
delete verifiers[slot];
|
|
101
|
+
try {
|
|
102
|
+
const m = material(slot, env);
|
|
103
|
+
verifiers[slot] = { publicKey: (0, node_crypto_1.createPublicKey)(signingKey(m.key, slot)).export({ format: 'der', type: 'spki' }).toString('base64'),
|
|
104
|
+
bindingSha256: sha(m.binding) };
|
|
105
|
+
}
|
|
106
|
+
catch { /* canal não provisionado não ganha autoridade de verificação */ }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return Object.keys(verifiers).length ? JSON.stringify({ schema: 'ork.hitl-verifiers/v1', verifiers }) : undefined;
|
|
110
|
+
}
|
|
111
|
+
function publicReceiptAuthority(canal, native) {
|
|
112
|
+
return inherited(process.env)[slotFor(canal, native)];
|
|
113
|
+
}
|
|
114
|
+
function publicBindingMatches(authority, binding) {
|
|
115
|
+
return authority.bindingSha256 === sha(binding);
|
|
116
|
+
}
|
|
117
|
+
/** Sidecar aditivo: recibo HMAC original e hash do ledger permanecem intactos. */
|
|
118
|
+
function signPublicReceipt(file, canal, native) {
|
|
119
|
+
const slot = slotFor(canal, native), m = material(slot, process.env);
|
|
120
|
+
const bytes = readRegular(file, 1048576);
|
|
121
|
+
const receipt = JSON.parse(bytes.toString('utf8'));
|
|
122
|
+
if (!/^[a-f0-9]{64}$/.test(receipt.assinatura ?? '') || !(0, node_crypto_1.timingSafeEqual)(Buffer.from(receipt.assinatura, 'hex'), (0, node_crypto_1.createHmac)('sha256', m.key).update(JSON.stringify(receipt.dados)).digest()))
|
|
123
|
+
throw Error('hitl.receipt.mac-invalid');
|
|
124
|
+
const proof = JSON.stringify({ schema: 'ork.hitl-public-receipt/v1',
|
|
125
|
+
signature: (0, node_crypto_1.sign)(null, bytes, signingKey(m.key, slot)).toString('base64') });
|
|
126
|
+
const proofFile = `${file}.public`;
|
|
127
|
+
const temporary = path.join(path.dirname(file), `${(0, node_crypto_1.randomUUID)()}.public.tmp`);
|
|
128
|
+
const fd = fs.openSync(temporary, 'wx', 0o600);
|
|
129
|
+
try {
|
|
130
|
+
fs.writeFileSync(fd, proof);
|
|
131
|
+
fs.fsyncSync(fd);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
fs.closeSync(fd);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
try {
|
|
138
|
+
fs.linkSync(temporary, proofFile);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
if (e.code !== 'EEXIST')
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
const dir = fs.openSync(path.dirname(file), 'r');
|
|
145
|
+
try {
|
|
146
|
+
fs.fsyncSync(dir);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
fs.closeSync(dir);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
fs.unlinkSync(temporary);
|
|
154
|
+
}
|
|
155
|
+
// Não substitui um sidecar divergente ou symlink encontrado no estado.
|
|
156
|
+
if (readRegular(proofFile, 1024).toString('utf8') !== proof)
|
|
157
|
+
throw Error('hitl.receipt.proof-conflict');
|
|
158
|
+
}
|
|
159
|
+
function readRegular(file, max) {
|
|
160
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
161
|
+
try {
|
|
162
|
+
const st = fs.fstatSync(fd);
|
|
163
|
+
if (!st.isFile() || st.nlink !== 1 || st.uid !== process.getuid?.() || (st.mode & 0o777) !== 0o600 || st.size > max)
|
|
164
|
+
throw Error('hitl.receipt.proof-invalid');
|
|
165
|
+
return fs.readFileSync(fd);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
fs.closeSync(fd);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function verifyPublicReceipt(file, bytes, authority) {
|
|
172
|
+
const proof = zod_1.z.object({ schema: zod_1.z.literal('ork.hitl-public-receipt/v1'),
|
|
173
|
+
signature: zod_1.z.string().regex(/^[A-Za-z0-9+/]{86}==$/) }).strict().parse(JSON.parse(readRegular(`${file}.public`, 1024).toString('utf8')));
|
|
174
|
+
const key = (0, node_crypto_1.createPublicKey)({ key: Buffer.from(authority.publicKey, 'base64'), format: 'der', type: 'spki' });
|
|
175
|
+
return key.asymmetricKeyType === 'ed25519' && (0, node_crypto_1.verify)(null, bytes, key, Buffer.from(proof.signature, 'base64'));
|
|
176
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SEM_PERGUNTA_NO_RESUMO = exports.TETO_DE_DECISOES_DETALHADAS = exports.TETO_DE_ESPERAS_POR_MAQUINA = exports.TETO_DE_THREADS_NOMEADAS = exports.PERGUNTA_DO_RESUMO = exports.CONTRATO_RESUMO = void 0;
|
|
4
|
+
exports.resumirHitl = resumirHitl;
|
|
5
|
+
exports.linhasDasOutrasMaquinas = linhasDasOutrasMaquinas;
|
|
6
|
+
exports.linhasDasDecisoes = linhasDasDecisoes;
|
|
7
|
+
exports.linhaDasPerguntas = linhaDasPerguntas;
|
|
8
|
+
exports.linhaDosConsertos = linhaDosConsertos;
|
|
9
|
+
exports.nomearThreads = nomearThreads;
|
|
10
|
+
exports.linhaDoLoteGuardado = linhaDoLoteGuardado;
|
|
11
|
+
exports.textoDoResumo = textoDoResumo;
|
|
12
|
+
/**
|
|
13
|
+
* I-41 (D16), CAMADA 1: o resumo recorrente. UMA mensagem, nunca uma por item.
|
|
14
|
+
*
|
|
15
|
+
* Em 20/09 as 16:00 o cron religado disparou e a entrega mandou 75 mensagens em minutos no
|
|
16
|
+
* Telegram do dono, uma por item. A frase dele foi "de uma forma que humanos simplesmente nao
|
|
17
|
+
* compreendem". A causa nao era volume de perguntas: era `varrerPulse` mandar uma mensagem por
|
|
18
|
+
* item novo. A camada 1 troca isso por um resumo: quantos pendentes, quantos urgentes, quantos
|
|
19
|
+
* travando alguma coisa (e quais), quantos sem volta, e uma pergunta no fim.
|
|
20
|
+
*
|
|
21
|
+
* Quem monta a mensagem e ESTE modulo, no nucleo. Os quatro hosts homologados transportam texto
|
|
22
|
+
* pronto. Se um deles precisasse decidir como escrever um numero, a decisao estaria no arquivo
|
|
23
|
+
* errado, e o dono veria uma coisa no Claude Code e outra no Telegram.
|
|
24
|
+
*
|
|
25
|
+
* O texto para humano nunca sai em JSON. `resumirHitl` devolve dados; `textoDoResumo` devolve a
|
|
26
|
+
* mensagem do canal. Quem entrega chama a segunda.
|
|
27
|
+
*/
|
|
28
|
+
const horario_1 = require("./horario");
|
|
29
|
+
const decisao_autonoma_1 = require("./decisao-autonoma");
|
|
30
|
+
const hitl_classificacao_1 = require("./hitl-classificacao");
|
|
31
|
+
exports.CONTRATO_RESUMO = 'ork.hitl-resumo/v1';
|
|
32
|
+
/** A pergunta final, palavra por palavra como o dono a escreveu. */
|
|
33
|
+
exports.PERGUNTA_DO_RESUMO = 'Posso te mandar as perguntas agora?';
|
|
34
|
+
/**
|
|
35
|
+
* Quantos nomes curtos de thread cabem no texto antes do "e mais N".
|
|
36
|
+
*
|
|
37
|
+
* Nomear dezoito threads numa mensagem de Telegram devolve o problema pelo outro lado: a
|
|
38
|
+
* mensagem vira uma lista longa de novo. Cinco nomes cabem em duas linhas e o resto vira
|
|
39
|
+
* contagem; o comando que mostra todas fica na ultima linha.
|
|
40
|
+
*/
|
|
41
|
+
exports.TETO_DE_THREADS_NOMEADAS = 5;
|
|
42
|
+
function resumirHitl(itens, opcoes) {
|
|
43
|
+
const janelaMin = opcoes.janelaMin ?? hitl_classificacao_1.JANELA_PADRAO_MIN;
|
|
44
|
+
const contagem = (0, hitl_classificacao_1.contarClassificacoes)(itens, { quando: opcoes.quando, janelaMin, atoDoItem: opcoes.atoDoItem });
|
|
45
|
+
const prontas = opcoes.prontas ?? 0;
|
|
46
|
+
return {
|
|
47
|
+
contrato: exports.CONTRATO_RESUMO, consultadoEm: opcoes.quando, janelaMin, ...contagem,
|
|
48
|
+
prontas, acumuladas: Math.min(opcoes.acumuladas ?? 0, prontas), consertos: opcoes.consertos ?? 0,
|
|
49
|
+
pergunta: prontas > 0 ? exports.PERGUNTA_DO_RESUMO : null,
|
|
50
|
+
decisoes: [...(opcoes.decisoes ?? [])], acimaDoLimiar: [...(opcoes.acimaDoLimiar ?? [])],
|
|
51
|
+
...(opcoes.outrasMaquinas?.length ? { outrasMaquinas: opcoes.outrasMaquinas.map(m => ({ ...m, esperando: [...m.esperando] })) } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Quantas threads de outra maquina o resumo nomeia antes de contar o resto. */
|
|
55
|
+
exports.TETO_DE_ESPERAS_POR_MAQUINA = 3;
|
|
56
|
+
/**
|
|
57
|
+
* I-51 (RM-047): as linhas das outras maquinas, as mesmas nos dois canais. Informam; a resposta e
|
|
58
|
+
* na maquina onde a thread vive.
|
|
59
|
+
*/
|
|
60
|
+
function linhasDasOutrasMaquinas(r, marcador, recuo) {
|
|
61
|
+
const outras = r.outrasMaquinas ?? [];
|
|
62
|
+
if (!outras.length)
|
|
63
|
+
return [];
|
|
64
|
+
const linhas = [`${marcador}Em outras máquinas:`];
|
|
65
|
+
for (const m of outras) {
|
|
66
|
+
const esperando = m.esperando.length;
|
|
67
|
+
linhas.push(`${recuo}${m.maquina}: ${m.ativas} ${m.ativas === 1 ? 'thread ativa' : 'threads ativas'}` +
|
|
68
|
+
(esperando ? `; esperando você em ${esperando} (responda lá)` : ''));
|
|
69
|
+
const mostradas = m.esperando.slice(0, exports.TETO_DE_ESPERAS_POR_MAQUINA), sobra = esperando - mostradas.length;
|
|
70
|
+
for (const e of mostradas)
|
|
71
|
+
linhas.push(`${recuo} • ${e.thread} ${e.fase}${e.pergunta ? `: ${uma(e.pergunta, 80)}` : ''}`);
|
|
72
|
+
if (sobra > 0)
|
|
73
|
+
linhas.push(`${recuo} e mais ${sobra}: ork fabrica`);
|
|
74
|
+
}
|
|
75
|
+
return linhas;
|
|
76
|
+
}
|
|
77
|
+
/** Quantas decisoes o texto detalha antes de contar o resto: o resumo continua sendo UMA mensagem. */
|
|
78
|
+
exports.TETO_DE_DECISOES_DETALHADAS = 3;
|
|
79
|
+
/** Uma linha cortada no teto de uma linha de Telegram, sem quebra. */
|
|
80
|
+
const uma = (texto, teto = 160) => {
|
|
81
|
+
const limpo = String(texto).replace(/\s+/g, ' ').trim();
|
|
82
|
+
return limpo.length > teto ? `${limpo.slice(0, teto - 3)}...` : limpo;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* As linhas das decisoes tomadas sem perguntar, as mesmas nos dois canais (so o recuo e o marcador
|
|
86
|
+
* mudam). Efeito antes de mecanismo: primeiro o que foi decidido, depois o porque e o preco.
|
|
87
|
+
*/
|
|
88
|
+
function linhasDasDecisoes(r, marcador, recuo) {
|
|
89
|
+
if (!r.decisoes.length && !r.acimaDoLimiar.length)
|
|
90
|
+
return [];
|
|
91
|
+
const mostradas = r.decisoes.slice(-exports.TETO_DE_DECISOES_DETALHADAS), sobra = r.decisoes.length - mostradas.length;
|
|
92
|
+
return [
|
|
93
|
+
...(r.decisoes.length ? [`${marcador}Decidi sem te perguntar: ${r.decisoes.length}`] : []),
|
|
94
|
+
...mostradas.flatMap(d => [
|
|
95
|
+
`${recuo}• ${d.thread} ${d.fase}: ${uma(d.decidido)}`,
|
|
96
|
+
`${recuo} porquê: ${uma(d.porque, 120)}. Para mudar: ${uma(d.comoMudar, 120)}.`,
|
|
97
|
+
`${recuo} reverter agora: ${uma(d.custoDeReverter.agora, 80)}; depois: ${uma(d.custoDeReverter.depois, 80)}.`,
|
|
98
|
+
]),
|
|
99
|
+
...(sobra > 0 ? [`${recuo}e mais ${sobra}: ork decisao placar <thread>`] : []),
|
|
100
|
+
...r.acimaDoLimiar.map(f => `${recuo}${marcador ? '⚠️ ' : '! '}${f.thread} ${f.fase} já tomou ${f.decididas} decisões sozinha, ` +
|
|
101
|
+
`acima do limiar de ${decisao_autonoma_1.LIMIAR_DE_DECISOES_POR_FASE}: vale revisar.`),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
/** Quando nao ha pergunta, o resumo diz isso em vez de pedir licenca para mandar nada. */
|
|
105
|
+
exports.SEM_PERGUNTA_NO_RESUMO = 'Nada aqui pede resposta sua por este canal agora.';
|
|
106
|
+
/** A linha das perguntas prontas, uma so para os dois canais, pelo mesmo motivo da de baixo. */
|
|
107
|
+
function linhaDasPerguntas(n) {
|
|
108
|
+
return `Perguntas para você: ${n}`;
|
|
109
|
+
}
|
|
110
|
+
/** A linha dos pedidos que nao viraram pergunta, com a concordancia certa (A5). */
|
|
111
|
+
function linhaDosConsertos(n) {
|
|
112
|
+
return n === 1
|
|
113
|
+
? '1 pedido não virou pergunta; o conserto é nosso, não seu.'
|
|
114
|
+
: `${n} pedidos não viraram pergunta; o conserto é nosso, não seu.`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Os nomes curtos que entram no texto, mais a sobra contada.
|
|
118
|
+
*
|
|
119
|
+
* O nome curto e o `id` da thread, que e o que o dono ja le em `ork board`. O nome longo
|
|
120
|
+
* ("I-41 HITL invertido: decisao tomada, lote e pergunta rara") e titulo, nao endereco.
|
|
121
|
+
*/
|
|
122
|
+
function nomearThreads(threads, teto = exports.TETO_DE_THREADS_NOMEADAS) {
|
|
123
|
+
if (threads.length === 0)
|
|
124
|
+
return '';
|
|
125
|
+
const mostradas = threads.slice(0, teto), sobra = threads.length - mostradas.length;
|
|
126
|
+
return mostradas.join(', ') + (sobra > 0 ? ` e mais ${sobra}` : '');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A frase do lote guardado, uma so para os dois canais.
|
|
130
|
+
*
|
|
131
|
+
* Ela existe em um lugar de proposito: se cada canal escrevesse a sua, o dono leria uma coisa no
|
|
132
|
+
* Telegram e outra no terminal, e a paridade que o nucleo existe para garantir se perderia na
|
|
133
|
+
* primeira frase que alguem achou que podia melhorar so de um lado.
|
|
134
|
+
*/
|
|
135
|
+
function linhaDoLoteGuardado(n) {
|
|
136
|
+
return n === 1
|
|
137
|
+
? '1 pergunta continua guardada desde o resumo anterior.'
|
|
138
|
+
: `${n} perguntas continuam guardadas desde o resumo anterior.`;
|
|
139
|
+
}
|
|
140
|
+
/** Como o humano responde. Letra ou palavra curta; nunca identificador longo para colar. */
|
|
141
|
+
function linhaDeResposta(codigo) {
|
|
142
|
+
return codigo
|
|
143
|
+
? [`Responda com ${codigo} a (sim) ou ${codigo} b (agora não).`]
|
|
144
|
+
: ['Responda: a (sim) ou b (agora não).'];
|
|
145
|
+
}
|
|
146
|
+
/** O fecho do resumo: a pergunta do dono quando ha o que mandar, e a frase honesta quando nao ha. */
|
|
147
|
+
function fecho(r, codigo) {
|
|
148
|
+
return r.pergunta ? [r.pergunta, ...linhaDeResposta(codigo)] : [exports.SEM_PERGUNTA_NO_RESUMO];
|
|
149
|
+
}
|
|
150
|
+
function textoTelegram(r, codigo) {
|
|
151
|
+
const quando = (0, horario_1.formatarDataHoraRotulada)(r.consultadoEm, { agora: r.consultadoEm });
|
|
152
|
+
const linhas = [
|
|
153
|
+
`🔔 Orkastery, resumo de ${quando}`,
|
|
154
|
+
'',
|
|
155
|
+
`Esperando você: ${r.total}`,
|
|
156
|
+
`⏱ Urgentes: ${r.urgentes}`,
|
|
157
|
+
`⛔ Travando o avanço: ${r.bloqueantes}${r.threadsBloqueadas.length ? `, em ${r.threadsBloqueadas.length} threads` : ''}`,
|
|
158
|
+
...(r.threadsBloqueadas.length ? [` ${nomearThreads(r.threadsBloqueadas)}`] : []),
|
|
159
|
+
`🔒 Sem volta depois de feito: ${r.criticos}`,
|
|
160
|
+
`❓ ${linhaDasPerguntas(r.prontas)}`,
|
|
161
|
+
...(r.consertos ? [`🔧 ${linhaDosConsertos(r.consertos)}`] : []),
|
|
162
|
+
...(r.acumuladas ? ['', `📥 ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
|
|
163
|
+
...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '🧭 ', ' ')] : []),
|
|
164
|
+
...(r.outrasMaquinas?.length ? ['', ...linhasDasOutrasMaquinas(r, '🖥️ ', ' ')] : []),
|
|
165
|
+
'',
|
|
166
|
+
...fecho(r, codigo),
|
|
167
|
+
];
|
|
168
|
+
return linhas.join('\n');
|
|
169
|
+
}
|
|
170
|
+
function textoTerminal(r, codigo) {
|
|
171
|
+
const quando = (0, horario_1.formatarDataHoraRotulada)(r.consultadoEm, { agora: r.consultadoEm });
|
|
172
|
+
const celulas = [
|
|
173
|
+
['esperando você', r.total],
|
|
174
|
+
['urgentes', r.urgentes],
|
|
175
|
+
['travando o avanço', r.bloqueantes],
|
|
176
|
+
['sem volta depois de feito', r.criticos],
|
|
177
|
+
['perguntas para você', r.prontas],
|
|
178
|
+
];
|
|
179
|
+
const largura = Math.max(...celulas.map(([nome]) => nome.length));
|
|
180
|
+
const numero = Math.max(7, ...celulas.map(([, n]) => String(n).length));
|
|
181
|
+
const regua = ` ${'-'.repeat(largura)} ${'-'.repeat(numero)}`;
|
|
182
|
+
const linhas = [
|
|
183
|
+
`Orkastery, resumo de ${quando}`,
|
|
184
|
+
'',
|
|
185
|
+
` ${'o que'.padEnd(largura)} ${'quantos'.padStart(numero)}`,
|
|
186
|
+
regua,
|
|
187
|
+
...celulas.map(([nome, n]) => ` ${nome.padEnd(largura)} ${String(n).padStart(numero)}`),
|
|
188
|
+
...(r.threadsBloqueadas.length
|
|
189
|
+
? ['', ` threads travadas (${r.threadsBloqueadas.length}): ${nomearThreads(r.threadsBloqueadas)}`] : []),
|
|
190
|
+
...(r.consertos ? ['', ` ${linhaDosConsertos(r.consertos)}`] : []),
|
|
191
|
+
...(r.acumuladas ? ['', ` ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
|
|
192
|
+
...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
|
|
193
|
+
...(r.outrasMaquinas?.length ? ['', ...linhasDasOutrasMaquinas(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
|
|
194
|
+
'',
|
|
195
|
+
...fecho(r, codigo),
|
|
196
|
+
];
|
|
197
|
+
return linhas.join('\n');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* A mensagem do canal. Nunca JSON: o dono le texto, e o Telegram nao formata objeto.
|
|
201
|
+
*
|
|
202
|
+
* `codigo` e o codigo curto do pedido de consentimento, quando ja existe um. Sem ele o texto
|
|
203
|
+
* ainda sai completo e diz como responder; o que nunca aparece e identificador longo para colar.
|
|
204
|
+
*/
|
|
205
|
+
function textoDoResumo(resumo, opcoes) {
|
|
206
|
+
if (resumo.contrato !== exports.CONTRATO_RESUMO)
|
|
207
|
+
throw new Error('resumo HITL: contrato inválido');
|
|
208
|
+
return opcoes.canal === 'telegram' ? textoTelegram(resumo, opcoes.codigo) : textoTerminal(resumo, opcoes.codigo);
|
|
209
|
+
}
|