@orkastery/cli 0.4.3 → 0.5.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.
Files changed (121) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/agents/ork-check.md +1 -1
  4. package/adapters/claude-code/agents/ork-go.md +2 -2
  5. package/adapters/claude-code/agents/ork-goal.md +1 -1
  6. package/adapters/claude-code/agents/ork-master.md +1 -1
  7. package/adapters/claude-code/agents/ork-plan.md +1 -1
  8. package/adapters/claude-code/agents/ork-ship.md +1 -1
  9. package/adapters/claude-code/commands/master.md +11 -8
  10. package/adapters/claude-code/commands/ork.md +8 -0
  11. package/adapters/claude-code/commands/ship.md +2 -2
  12. package/adapters/codex/skills/ork/SKILL.md +12 -1
  13. package/adapters/hermes/README.md +3 -0
  14. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  15. package/adapters/hermes/bin/ork-brain.sh +3 -0
  16. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  17. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  18. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  19. package/adapters/hermes/hermes.plugin.json +2 -0
  20. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  21. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  22. package/adapters/openclaw/README.md +34 -2
  23. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  24. package/adapters/openclaw/dist/index.js +92 -20
  25. package/adapters/openclaw/openclaw.plugin.json +10 -1
  26. package/adapters/openclaw/package.json +2 -2
  27. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  28. package/adapters/openclaw/src/index.ts +108 -20
  29. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  30. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  31. package/assets/docs/produto/_modelo-feature.md +2 -0
  32. package/assets/orkmind_bridge.py +214 -1
  33. package/dist/adapters/claude-bg.js +6 -3
  34. package/dist/board.js +74 -0
  35. package/dist/branch-de-estado.js +5 -4
  36. package/dist/busca-semantica.js +161 -0
  37. package/dist/ci.js +141 -10
  38. package/dist/company-brain-cli.js +27 -2
  39. package/dist/company-brain-context.js +132 -0
  40. package/dist/company-brain-dossie.js +376 -0
  41. package/dist/company-brain-mcp.js +4 -1
  42. package/dist/conducao.js +71 -18
  43. package/dist/decisao-autonoma.js +42 -0
  44. package/dist/docs.js +64 -22
  45. package/dist/doctor.js +33 -0
  46. package/dist/entrega-pr.js +109 -5
  47. package/dist/experiencia-instalacao.js +256 -0
  48. package/dist/experiencia.js +72 -0
  49. package/dist/fabrica-estado.js +43 -10
  50. package/dist/fechamento.js +105 -0
  51. package/dist/forja.js +435 -0
  52. package/dist/gates.js +9 -1
  53. package/dist/hitl-classificacao.js +11 -1
  54. package/dist/hitl-contract.js +50 -7
  55. package/dist/hitl-curto.js +158 -0
  56. package/dist/hitl-gates.js +67 -1
  57. package/dist/hitl-lote.js +30 -15
  58. package/dist/hitl-presentation.js +10 -7
  59. package/dist/hitl-resumo.js +33 -12
  60. package/dist/hitl-texto-livre.js +150 -0
  61. package/dist/hosts.js +86 -1
  62. package/dist/index.js +602 -38
  63. package/dist/indice-vetorial.js +317 -0
  64. package/dist/init.js +13 -0
  65. package/dist/integracoes-locais.js +1 -1
  66. package/dist/intelligence-benchmark-contract.js +570 -0
  67. package/dist/intelligence-graph-cli.js +430 -0
  68. package/dist/intelligence-graph-contract.js +577 -0
  69. package/dist/intelligence-graph-extract-md.js +469 -0
  70. package/dist/intelligence-graph-extract-ts.js +810 -0
  71. package/dist/intelligence-graph-extract.js +369 -0
  72. package/dist/intelligence-graph-index.js +512 -0
  73. package/dist/intelligence-graph-parsers.js +327 -0
  74. package/dist/intelligence-graph-query.js +344 -0
  75. package/dist/intelligence-graph-repo.js +187 -0
  76. package/dist/leases.js +84 -0
  77. package/dist/ledger.js +14 -0
  78. package/dist/maestro-cli.js +10 -1
  79. package/dist/maestro-contract.js +5 -1
  80. package/dist/maestro-discovery.js +16 -2
  81. package/dist/maestro-snapshot.js +6 -2
  82. package/dist/manifest.js +142 -5
  83. package/dist/master-batch.js +3 -2
  84. package/dist/master-digest.js +2 -2
  85. package/dist/master-nota.js +203 -0
  86. package/dist/master.js +4 -0
  87. package/dist/mcp-experiencia.js +164 -0
  88. package/dist/mcp-git.js +4 -0
  89. package/dist/mcp-install.js +3 -1
  90. package/dist/mcp-maestro.js +3 -2
  91. package/dist/mcp-server.js +84 -5
  92. package/dist/memoria.js +147 -1
  93. package/dist/network-roadmap.js +907 -0
  94. package/dist/ocupacao.js +38 -2
  95. package/dist/onboarding.js +113 -7
  96. package/dist/orkmind.js +165 -9
  97. package/dist/phase.js +166 -13
  98. package/dist/policies.js +2 -0
  99. package/dist/projeto-alvo.js +551 -0
  100. package/dist/prompts.js +1 -1
  101. package/dist/pulse-consentimento.js +4 -2
  102. package/dist/pulse-delivery.js +5 -0
  103. package/dist/pulse-resposta.js +674 -49
  104. package/dist/pulse.js +21 -0
  105. package/dist/retry.js +51 -8
  106. package/dist/roadmap-reservas.js +262 -20
  107. package/dist/roadmap-status.js +213 -0
  108. package/dist/thread-close.js +3 -0
  109. package/dist/verify.js +4 -3
  110. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  111. package/eval/casos/orchestration-experience.json +48 -0
  112. package/package.json +1 -1
  113. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  114. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  115. package/schemas/maestro-snapshot.schema.json +23 -0
  116. package/skills/README.md +7 -5
  117. package/skills/core/onboarding/SKILL.md +7 -0
  118. package/skills/core/orchestration-experience/SKILL.md +113 -0
  119. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  120. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  121. package/skills/phases/go-implementation/SKILL.md +2 -0
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TEXTO_DO_ATO = exports.TETO_DE_LINHAS_DO_CORPO = exports.TETO_DE_LINHAS_DO_PEDIDO = exports.CONTRATO_PEDIDO_CURTO = void 0;
4
+ exports.entradaDoPedido = entradaDoPedido;
5
+ exports.montarPedidoCurto = montarPedidoCurto;
6
+ exports.linhaDeResposta = linhaDeResposta;
7
+ exports.textoDoPedidoCurto = textoDoPedidoCurto;
8
+ exports.desdeDoPedido = desdeDoPedido;
9
+ /**
10
+ * RM-048 (itens 1, 4 e 5): o contrato UNICO de apresentacao de um pedido ao dono.
11
+ *
12
+ * Ate aqui o mesmo pedido saia com tres textos diferentes: o dialogo do host (`apresentarDecisao`),
13
+ * o lote do Telegram (`textoDoLote`) e o item do pulse, que levava um comando com o identificador
14
+ * longo. Cada um gastava de tres a quatro linhas por alternativa, e o dono, que conduz dezenas de
15
+ * frentes ao mesmo tempo, tinha de reler a mensagem para achar a pergunta. O pedido do dono foi
16
+ * este: pergunta curta, alternativas de uma linha, sempre uma recomendada, e uma linha que diz
17
+ * como responder.
18
+ *
19
+ * O contrato tem sete partes e um teto, e o teto e garantido por construcao, nao por revisao:
20
+ *
21
+ * titulo de onde vem (thread e fase), em uma linha;
22
+ * pergunta o que se decide, em uma frase;
23
+ * trava o que esta parado, desde quando, e o que acontece sem resposta;
24
+ * sem volta so quando responder autoriza um ato da lista congelada;
25
+ * corpo no maximo `TETO_DE_LINHAS_DO_CORPO` linhas de evidencia;
26
+ * alternativas de 2 a 4, uma linha cada, com a consequencia, e UMA recomendada com o porque;
27
+ * responder a ultima linha, dizendo exatamente o que digitar.
28
+ *
29
+ * O detalhe profundo (artefato, claims, riscos, diff) fica a um pedido de distancia: a ultima
30
+ * linha diz como pedir, e o nucleo responde com o bloco de evidencia (`<codigo> detalhes`).
31
+ *
32
+ * Telegram e terminal carregam o MESMO conteudo; muda o marcador (icone no Telegram, texto no
33
+ * terminal). OpenClaw transporta o texto do Telegram. Quem monta e o nucleo; o host so transporta.
34
+ */
35
+ const hitl_1 = require("./hitl");
36
+ const hitl_contract_1 = require("./hitl-contract");
37
+ const horario_1 = require("./horario");
38
+ exports.CONTRATO_PEDIDO_CURTO = 'ork.hitl-curto/v1';
39
+ /** Nenhum pedido passa disto por padrao (RM-048, criterio c). */
40
+ exports.TETO_DE_LINHAS_DO_PEDIDO = 15;
41
+ /** Evidencia que cabe na pergunta curta; o resto vai no detalhe. */
42
+ exports.TETO_DE_LINHAS_DO_CORPO = 2;
43
+ /** Teto de uma linha logica: alternativa com consequencia ainda e leitura rapida. */
44
+ const TETO_DA_LINHA = 200;
45
+ /** O texto de cada ato congelado, como o dono o le. */
46
+ exports.TEXTO_DO_ATO = Object.freeze({
47
+ dinheiro: 'gasta dinheiro',
48
+ 'publicacao-externa': 'publica fora do projeto',
49
+ 'apagar-dado': 'apaga dado',
50
+ 'push-base-protegida': 'faz push na base protegida',
51
+ });
52
+ const linha = (texto, teto = TETO_DA_LINHA) => {
53
+ const limpo = (0, hitl_1.redigirSegredos)(String(texto ?? '')).replace(/\s+/g, ' ').trim();
54
+ return limpo.length > teto ? `${limpo.slice(0, teto - 3)}...` : limpo;
55
+ };
56
+ /** A entrada de um pedido v2 `pergunta`. Um v1 chega pelo lote, que ja o traduziu. */
57
+ function entradaDoPedido(pedido, desde) {
58
+ (0, hitl_contract_1.validarPedidoHitl)(pedido);
59
+ if (!(0, hitl_contract_1.ehV2)(pedido) || pedido.classe !== 'pergunta') {
60
+ throw new Error('pedido curto: só uma pergunta ork.hitl/v2 tem alternativas declaradas');
61
+ }
62
+ const alvo = (0, hitl_contract_1.alvoDoPedido)(pedido);
63
+ return {
64
+ thread: pedido.thread, fase: pedido.fase, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido),
65
+ alternativas: pedido.alternativas.map(a => ({ chave: a.letra, texto: a.texto, consequencia: a.consequencia,
66
+ ...(a.recomendada ? { recomendada: { porque: a.porque ?? '' } } : {}) })),
67
+ alvo: alvo?.tipo === 'session' ? { tipo: 'session', sessionId: alvo.sessionId } : { tipo: 'gate' },
68
+ corpo: pedido.corpo, ...(pedido.ato ? { ato: pedido.ato } : {}),
69
+ expiracao: (0, hitl_contract_1.expiracaoDoPedido)(pedido), prazo: (0, hitl_contract_1.prazoDoPedido)(pedido) ?? null, desde: desde ?? null,
70
+ };
71
+ }
72
+ /** O que acontece sem resposta, em meia linha. Expirar nunca autoriza. */
73
+ function semResposta(e, agora) {
74
+ const ate = e.prazo ? ` até ${(0, horario_1.formatarHora)(e.prazo, { agora })}` : '';
75
+ if (e.expiracao === 'seguir-recomendada')
76
+ return `Sem resposta${ate}, sigo com a recomendada.`;
77
+ if (e.expiracao === 'escalar')
78
+ return `Sem resposta${ate}, o pedido é escalado.`;
79
+ return e.alvo?.tipo === 'session' ? 'Sem resposta, a sessão continua parada.' : 'Sem resposta, nada avança.';
80
+ }
81
+ /**
82
+ * Monta o pedido curto. Recusa, em vez de cortar em silencio, o que o contrato nao aceita: menos
83
+ * de duas alternativas, mais de quatro, ou recomendada diferente de exatamente uma.
84
+ */
85
+ function montarPedidoCurto(e, opcoes) {
86
+ if (e.alternativas.length < 2 || e.alternativas.length > 4)
87
+ throw new Error('pedido curto: de 2 a 4 alternativas');
88
+ if (e.alternativas.filter(a => a.recomendada).length !== 1)
89
+ throw new Error('pedido curto: exatamente uma alternativa é recomendada');
90
+ const onde = e.alvo?.tipo === 'session'
91
+ ? `A sessão ${e.alvo.sessionId.slice(0, 8)} está parada esperando você`
92
+ : `${e.fase ? `A fase ${e.fase}` : 'A thread'} está parada esperando você`;
93
+ const desde = e.desde ? ` desde ${(0, horario_1.formatarDesde)(e.desde, { agora: opcoes.quando })}` : '';
94
+ return {
95
+ contrato: exports.CONTRATO_PEDIDO_CURTO,
96
+ titulo: linha([e.thread, e.fase].filter(Boolean).join(' · ') || 'sem thread'),
97
+ pergunta: linha(e.pergunta),
98
+ trava: linha(`${onde}${desde}. ${semResposta(e, opcoes.quando)}`),
99
+ ...(e.ato ? { semVolta: `Sem volta depois de feito: ${exports.TEXTO_DO_ATO[e.ato]}.` } : {}),
100
+ corpo: (e.corpo ?? []).slice(0, exports.TETO_DE_LINHAS_DO_CORPO).map(l => linha(l)).filter(Boolean),
101
+ alternativas: e.alternativas.map(a => ({ chave: a.chave, texto: linha(a.texto, 140), consequencia: linha(a.consequencia, 140),
102
+ ...(a.recomendada ? { recomendada: { porque: linha(a.recomendada.porque, 140) } } : {}) })),
103
+ responder: opcoes.responder,
104
+ };
105
+ }
106
+ /** A ultima linha: exatamente o que digitar, sem identificador longo. */
107
+ function linhaDeResposta(p, canal) {
108
+ const r = p.responder, recomendada = p.alternativas.find(a => a.recomendada).chave;
109
+ if (r.tipo === 'numero')
110
+ return null;
111
+ if (r.tipo === 'texto')
112
+ return `${canal === 'telegram' ? '↩️ ' : ''}Responda escrevendo o texto; ele é enviado como está.`;
113
+ if (r.tipo === 'dialogo')
114
+ return `${canal === 'telegram' ? '↩️ ' : ''}Responda selecionando a opção no diálogo; digitar a letra também vale.`;
115
+ return canal === 'telegram'
116
+ ? `↩️ Responda: ${r.codigo} ${recomendada} (ou outra letra). Evidências: ${r.codigo} detalhes.`
117
+ : `Responda pelo Telegram: ${r.codigo} ${recomendada} (ou outra letra); aqui, pelo diálogo do host. Evidências: ${r.codigo} detalhes.`;
118
+ }
119
+ /**
120
+ * O texto do pedido no canal. Telegram e terminal dizem a mesma coisa com marcadores diferentes;
121
+ * nenhum dos dois passa de `TETO_DE_LINHAS_DO_PEDIDO` linhas.
122
+ */
123
+ function textoDoPedidoCurto(p, canal) {
124
+ if (p.contrato !== exports.CONTRATO_PEDIDO_CURTO)
125
+ throw new Error('pedido curto: contrato inválido');
126
+ const tg = canal === 'telegram';
127
+ const numero = p.responder.tipo === 'numero' ? `${p.responder.numero}. ` : '';
128
+ const linhas = [
129
+ `${tg && !numero ? '🔔 ' : ''}${numero}${tg || numero ? '' : 'Orkastery · '}${p.titulo}`,
130
+ `${tg ? '❓ ' : 'Pergunta: '}${p.pergunta}`,
131
+ `${tg ? '⏳ ' : 'Situação: '}${p.trava}`,
132
+ ...(p.semVolta ? [`${tg ? '🔒 ' : '! '}${p.semVolta}`] : []),
133
+ ...p.corpo.map(l => `${tg ? '• ' : '- '}${l}`),
134
+ ...p.alternativas.flatMap(a => [
135
+ `${a.chave}) ${a.texto}${a.recomendada ? (tg ? ' ✅ recomendada' : ' [recomendada]') : ''}: ${a.consequencia}`,
136
+ ...(a.recomendada ? [` porquê: ${a.recomendada.porque}`] : []),
137
+ ]),
138
+ ];
139
+ const resposta = linhaDeResposta(p, canal);
140
+ if (resposta)
141
+ linhas.push(resposta);
142
+ if (linhas.length > exports.TETO_DE_LINHAS_DO_PEDIDO)
143
+ throw new Error('pedido curto: acima do teto de linhas');
144
+ return linhas.join('\n');
145
+ }
146
+ /**
147
+ * Desde quando o dono e esperado nesta pergunta: o primeiro pedido do ledger com o mesmo codigo
148
+ * no mesmo contexto. Com o codigo estavel (D4), renovar o pedido nao zera o relogio que o dono ve.
149
+ */
150
+ function desdeDoPedido(eventos, pedido) {
151
+ const codigo = pedido.codigo;
152
+ const doPedido = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === pedido.id);
153
+ if (typeof codigo !== 'string' || !doPedido)
154
+ return pedido.criadoEm;
155
+ const primeiro = eventos.find(e => e.tipo === 'hitl_requested' && e.contexto === doPedido.contexto &&
156
+ e.pedido?.codigo === codigo);
157
+ return primeiro?.pedido?.criadoEm ?? pedido.criadoEm;
158
+ }
@@ -45,6 +45,8 @@ exports.resolverCriterio = resolverCriterio;
45
45
  exports.registrarPedidoHitl = registrarPedidoHitl;
46
46
  exports.abrirPedidoGate = abrirPedidoGate;
47
47
  exports.prepararPedidoGate = prepararPedidoGate;
48
+ exports.essenciaDoPedido = essenciaDoPedido;
49
+ exports.renovarPedidoDoGate = renovarPedidoDoGate;
48
50
  exports.conferirResposta = conferirResposta;
49
51
  exports.dadosDaResposta = dadosDaResposta;
50
52
  exports.receberResposta = receberResposta;
@@ -330,7 +332,13 @@ function prepararPedidoGate(raiz, id, motivo = 'human.pending', quando = new Dat
330
332
  const ato = (0, hitl_contract_1.atoDaPausa)(motivo, bloco.pausaSobre);
331
333
  // O codigo curto e unico contra os que ja estao no ledger desta thread. T11 acrescenta o
332
334
  // comando que o resolve; a unicidade precisa nascer com o primeiro pedido que o carrega.
333
- const codigo = (0, pulse_consentimento_1.gerarCodigo)(eventos.flatMap(e => {
335
+ // RM-048 (D4): o gate reaberto no MESMO contexto e pelo mesmo motivo reaproveita o codigo do
336
+ // pedido anterior. Era a troca de codigo a cada hora que fazia a linha recebida virar po.
337
+ // A2 do CHECK: gate ja respondido de forma definitiva (aprovado ou recusado) neste contexto nao
338
+ // empresta o codigo: o que renasce depois dele e outra pergunta, com outro codigo.
339
+ const definitivo = gateRespondidoDeVez(eventos, contexto, motivo);
340
+ const anterior = definitivo ? undefined : pedidoAtual?.codigo;
341
+ const codigo = typeof anterior === 'string' ? anterior : (0, pulse_consentimento_1.gerarCodigo)(eventos.flatMap(e => {
334
342
  const c = e.pedido?.codigo;
335
343
  return typeof c === 'string' ? [c] : [];
336
344
  }));
@@ -353,8 +361,66 @@ function prepararPedidoGate(raiz, id, motivo = 'human.pending', quando = new Dat
353
361
  tipoDeResposta: 'objetiva', irreversivel: ato !== undefined, ...(ato ? { ato } : {}), codigo,
354
362
  prazo: new Date(Date.parse(quando) + 60 * 60 * 1000).toISOString(), acaoPadraoAoExpirar: 'esperar',
355
363
  respostaAceita: { tipo: 'opcao', maxCaracteres: 100 } };
364
+ // RM-048 (D4): um codigo quer dizer UMA pergunta. Se o que sairia agora nao e o que o dono leu
365
+ // com este codigo (a recomendada mudou, o diagnostico mudou), o codigo e outro: "DE6H a" nunca
366
+ // se aplica a uma pergunta que o dono nao viu.
367
+ if (typeof anterior === 'string' && pedidoAtual && essenciaDoPedido(pedido) !== essenciaDoPedido(pedidoAtual)) {
368
+ pedido.codigo = (0, pulse_consentimento_1.gerarCodigo)(eventos.flatMap(e => {
369
+ const c = e.pedido?.codigo;
370
+ return typeof c === 'string' ? [c] : [];
371
+ }));
372
+ }
356
373
  return { novo: pedido };
357
374
  }
375
+ /** Algum pedido deste gate (contexto e motivo) ja recebeu veredito definitivo (nao `aguardando`). */
376
+ function gateRespondidoDeVez(eventos, contexto, motivo) {
377
+ const ids = new Set(eventos.filter(e => e.tipo === 'hitl_requested' && e.contexto === contexto &&
378
+ (0, hitl_contract_1.alvoDoPedido)(e.pedido)?.tipo === 'gate' && (0, hitl_contract_1.motivoDoPedido)(e.pedido) === motivo)
379
+ .map(e => e.pedido.id));
380
+ return eventos.some(e => e.tipo === 'human_gate' && ids.has(String(e.pedidoId)) && e.estado !== 'aguardando');
381
+ }
382
+ /**
383
+ * RM-048 (D4): o que faz dois pedidos de gate serem a MESMA pergunta para o dono. O identificador,
384
+ * o instante e o prazo mudam a cada renovacao; o que o dono leu e respondeu nao pode mudar.
385
+ */
386
+ function essenciaDoPedido(p) {
387
+ if (!(0, hitl_contract_1.ehV2)(p) || p.classe !== 'pergunta')
388
+ return JSON.stringify(['v1', p.id]);
389
+ return JSON.stringify([p.thread, p.fase, p.modo, p.alvo, p.motivo, p.pergunta, p.alternativas, p.corpo,
390
+ p.tipoDeResposta, p.irreversivel, p.ato ?? null, p.codigo]);
391
+ }
392
+ /**
393
+ * RM-048 (D4): a resposta a um pedido de gate vencido vai para o pedido RENOVADO, e so quando a
394
+ * pergunta e a mesma.
395
+ *
396
+ * O prazo de uma hora existe para que uma aprovacao velha nao se aplique a um mundo novo. Aqui o
397
+ * mundo e conferido de novo, e nao presumido: o contexto da thread (modo, fase, status, base,
398
+ * despacho) tem de ser o do pedido original, o gate tem de continuar esperando o dono agora
399
+ * (`prepararPedidoGate`, com as mesmas recusas de sempre), e a essencia do pedido que sairia
400
+ * agora tem de ser identica a do que o dono leu. Qualquer diferenca e recusa com "pedido antigo":
401
+ * a letra que o dono digitou nunca e aplicada a uma pergunta que ele nao viu.
402
+ *
403
+ * Nao escolhe resposta, nao aprova nada e nao toca a prova: quem registra e `responderGate`, com
404
+ * o envelope assinado pelo ingresso, contra o pedido que esta funcao devolve.
405
+ */
406
+ function renovarPedidoDoGate(raiz, original, quando) {
407
+ (0, hitl_contract_1.validarPedidoHitl)(original);
408
+ if ((0, hitl_contract_1.alvoDoPedido)(original)?.tipo !== 'gate')
409
+ throw new Error('pedido antigo: só pedido de gate é renovado');
410
+ const t = (0, thread_1.lerThread)(raiz, original.thread), eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, original.thread));
411
+ const evento = eventos.find(e => e.tipo === 'hitl_requested' && e.pedido?.id === original.id);
412
+ if (!evento || evento.contexto !== contextoHitlDosEventos(t, eventos))
413
+ throw new Error('pedido antigo: fase, modo ou sessão mudou');
414
+ // A2 do CHECK: a linha velha nao desfaz um veredito definitivo dado por outra linha.
415
+ if (gateRespondidoDeVez(eventos, evento.contexto, (0, hitl_contract_1.motivoDoPedido)(original))) {
416
+ throw new Error('pedido antigo: o gate já foi respondido');
417
+ }
418
+ const preparado = prepararPedidoGate(raiz, original.thread, (0, hitl_contract_1.motivoDoPedido)(original), quando);
419
+ const candidato = 'aberto' in preparado ? preparado.aberto : preparado.novo;
420
+ if (essenciaDoPedido(candidato) !== essenciaDoPedido(original))
421
+ throw new Error('pedido antigo: a pergunta mudou desde que saiu');
422
+ return 'aberto' in preparado ? preparado.aberto : registrarPedidoHitl(raiz, preparado.novo);
423
+ }
358
424
  const LETRAS_DO_GATE = ['a', 'b', 'c'];
359
425
  /** Corta uma linha de evidencia no teto do corpo, sem deixar o pedido inteiro ser recusado. */
360
426
  const linhaDoCorpo = (texto) => texto.slice(0, hitl_contract_1.TETOS_HITL_V2.corpoLinha);
package/dist/hitl-lote.js CHANGED
@@ -30,6 +30,7 @@ exports.textoDoLote = textoDoLote;
30
30
  */
31
31
  const hitl_contract_1 = require("./hitl-contract");
32
32
  const hitl_1 = require("./hitl");
33
+ const hitl_curto_1 = require("./hitl-curto");
33
34
  exports.CONTRATO_LOTE = 'ork.hitl-lote/v1';
34
35
  /** De cinco em cinco, como o dono pediu. As que sobram continuam guardadas, nao somem. */
35
36
  exports.TETO_DE_PERGUNTAS_POR_LOTE = 5;
@@ -86,6 +87,16 @@ function alternativaRecomendada(pedido) {
86
87
  .filter(o => o.achou);
87
88
  return citadas.length === 1 ? citadas[0].i : -1;
88
89
  }
90
+ /** O que o contrato curto le do pedido alem das alternativas: alvo, corpo, ato e expiracao. */
91
+ function contextoDaPergunta(pedido) {
92
+ const alvo = (0, hitl_contract_1.alvoDoPedido)(pedido);
93
+ const v2 = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido : undefined;
94
+ return {
95
+ alvo: alvo?.tipo === 'session' ? { tipo: 'session', sessionId: alvo.sessionId } : { tipo: 'gate' },
96
+ ...(v2 ? { corpo: [...v2.corpo] } : {}), ...(v2?.ato ? { ato: v2.ato } : {}),
97
+ expiracao: (0, hitl_contract_1.expiracaoDoPedido)(pedido), prazo: (0, hitl_contract_1.prazoDoPedido)(pedido) ?? null,
98
+ };
99
+ }
89
100
  /**
90
101
  * Traduz um pedido em pergunta do lote, ou diz por que ele nao virou uma.
91
102
  *
@@ -105,7 +116,8 @@ function perguntaDoPedido(pedido, numero) {
105
116
  return recusa('pergunta vazia');
106
117
  const declaradas = alternativasDeclaradas(pedido);
107
118
  if (declaradas) {
108
- return { numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, alternativas: declaradas, pedidoId: pedido.id };
119
+ return { numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, alternativas: declaradas, pedidoId: pedido.id,
120
+ ...contextoDaPergunta(pedido) };
109
121
  }
110
122
  // I-41 (T4c): o parser da recomendacao e exclusivo do v1, e a porta se fecha aqui.
111
123
  //
@@ -125,7 +137,7 @@ function perguntaDoPedido(pedido, numero) {
125
137
  if (recomendada < 0)
126
138
  return recusa('a recomendação não nomeia exatamente uma alternativa');
127
139
  return {
128
- numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, pedidoId: pedido.id,
140
+ numero, thread: pedido.thread ?? null, fase: pedido.fase ?? null, pergunta, pedidoId: pedido.id, ...contextoDaPergunta(pedido),
129
141
  alternativas: opcoes.map((o, i) => ({
130
142
  letra: exports.LETRAS[i], texto: linha(o.texto), consequencia: exports.CONSEQUENCIA_DA_ACAO[o.acao],
131
143
  ...(i === recomendada ? { recomendada: { porque: linha((0, hitl_contract_1.recomendacaoDoPedido)(pedido)) } } : {}),
@@ -172,17 +184,18 @@ function montarLote(pedidos, opcoes = {}) {
172
184
  function guardadasDepoisDoLote(lote) {
173
185
  return lote.restantes;
174
186
  }
175
- function corpoDaPergunta(p, marca) {
176
- const cabecalho = [p.thread, p.fase].filter(Boolean).join(' · ');
177
- return [
178
- `${p.numero}. ${cabecalho || 'sem thread'}`,
179
- ` ${p.pergunta}`,
180
- ...p.alternativas.flatMap(a => [
181
- ` ${a.letra}) ${a.texto}${a.recomendada ? ` ${marca}recomendada` : ''}`,
182
- ` → ${a.consequencia}`,
183
- ...(a.recomendada ? [` porquê: ${a.recomendada.porque}`] : []),
184
- ]),
185
- ];
187
+ /**
188
+ * RM-048 (D1): cada pergunta do lote sai pelo contrato curto, com o numero no titulo. A linha de
189
+ * como responder e uma so, no fim do lote, porque o dono responde todas numa mensagem: "1a 2c".
190
+ */
191
+ function corpoDaPergunta(p, canal, quando) {
192
+ const curto = (0, hitl_curto_1.montarPedidoCurto)({
193
+ thread: p.thread, fase: p.fase, pergunta: p.pergunta, alvo: p.alvo, corpo: p.corpo, ato: p.ato,
194
+ expiracao: p.expiracao, prazo: p.prazo, desde: p.desde,
195
+ alternativas: p.alternativas.map(a => ({ chave: a.letra, texto: a.texto, consequencia: a.consequencia,
196
+ ...(a.recomendada ? { recomendada: { porque: a.recomendada.porque } } : {}) })),
197
+ }, { quando, responder: { tipo: 'numero', numero: p.numero } });
198
+ return (0, hitl_curto_1.textoDoPedidoCurto)(curto, canal).split('\n');
186
199
  }
187
200
  /** A5: a frase dos pedidos que nao viraram pergunta, com a concordancia certa para o dono ler. */
188
201
  function linhaDasRecusas(n) {
@@ -200,15 +213,17 @@ function textoDoLote(lote, opcoes) {
200
213
  if (lote.contrato !== exports.CONTRATO_LOTE)
201
214
  throw new Error('lote HITL: contrato inválido');
202
215
  const telegram = opcoes.canal === 'telegram';
216
+ const quando = opcoes.quando ?? new Date().toISOString();
203
217
  const n = lote.perguntas.length;
204
218
  const exemplo = lote.perguntas.map(p => `${p.numero}${p.alternativas[0].letra}`).join(' ');
205
219
  const faltam = opcoes.proximo?.faltam ?? lote.restantes;
206
220
  return [
207
221
  `${telegram ? '📋 ' : ''}Orkastery, ${n === 1 ? '1 pergunta' : `${n} perguntas`}`,
208
222
  '',
209
- ...lote.perguntas.flatMap((p, i) => [...corpoDaPergunta(p, telegram ? '✅ ' : '← '), ...(i < n - 1 ? [''] : [])]),
223
+ ...lote.perguntas.flatMap((p, i) => [...corpoDaPergunta(p, opcoes.canal, quando), ...(i < n - 1 ? [''] : [])]),
210
224
  '',
211
- ...(n ? [`Responda com o número e a letra, por exemplo: ${exemplo}.`] : ['Nenhuma pergunta em formato respondível agora.']),
225
+ ...(n ? [`${telegram ? '↩️ ' : ''}Responda com o número e a letra, por exemplo: ${exemplo}. Evidências: ${lote.perguntas[0].numero} detalhes.`]
226
+ : ['Nenhuma pergunta em formato respondível agora.']),
212
227
  ...(opcoes.proximo && faltam
213
228
  ? [`Falta${faltam === 1 ? '' : 'm'} ${faltam}. Para receber as próximas, responda ${opcoes.proximo.codigo} a.`]
214
229
  : faltam ? [`Sobra${faltam === 1 ? '' : 'm'} ${faltam} para o próximo lote.`] : []),
@@ -51,6 +51,7 @@ const hitl_contract_1 = require("./hitl-contract");
51
51
  const hitl_canais_1 = require("./hitl-canais");
52
52
  const hitl_native_offer_1 = require("./hitl-native-offer");
53
53
  const horario_1 = require("./horario");
54
+ const hitl_curto_1 = require("./hitl-curto");
54
55
  /**
55
56
  * I-35: prazo do pedido no fuso do dono, absoluto e rotulado, para JSON de exibição
56
57
  * (`ork gate context` e `ork_hitl_pending`). `pedido.prazo` continua o ISO assinado.
@@ -66,25 +67,27 @@ function prazoLocalDoPedido(pedido, agora) {
66
67
  * e o que ele troca é o número pela letra. Um `decidido` não passa por aqui de propósito: ele
67
68
  * não tem escolha a oferecer, e montar um diálogo para um fato consumado é pedir ação por engano.
68
69
  */
69
- function apresentarDecisao(pedido, agora) {
70
+ function apresentarDecisao(pedido, agora, desde) {
70
71
  (0, hitl_contract_1.validarPedidoHitl)(pedido);
71
72
  const aceita = (0, hitl_contract_1.respostaAceitaDoPedido)(pedido);
72
73
  if (!aceita)
73
74
  throw new Error('decisão informada não tem diálogo: ela não pede nada');
74
75
  const livre = aceita.tipo === 'texto';
75
76
  const escolhas = (0, hitl_contract_1.escolhasDoPedido)(pedido).map(o => ({ const: (0, hitl_contract_1.chaveDaEscolha)(pedido, o.numero), title: (0, hitl_1.redigirSegredos)(o.texto) }));
76
- const v2 = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido : undefined;
77
+ // RM-048 (D1): a pergunta v2 sai pelo contrato curto, o mesmo do Telegram e do lote. O v1 fica
78
+ // byte a byte abaixo: recibo antigo continua batendo com o texto que o dono viu.
79
+ if ((0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta') {
80
+ const curto = (0, hitl_curto_1.montarPedidoCurto)((0, hitl_curto_1.entradaDoPedido)(pedido, desde ?? pedido.criadoEm), { quando: agora ?? new Date().toISOString(), responder: livre ? { tipo: 'texto' } : { tipo: 'dialogo' } });
81
+ return { mensagem: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'terminal'), escolhas };
82
+ }
77
83
  const expiracao = (0, hitl_contract_1.expiracaoDoPedido)(pedido);
78
84
  const linhas = [
79
85
  `Orkastery · ${pedido.fase}`,
80
86
  `• Decisão: ${(0, hitl_1.redigirSegredos)((0, hitl_contract_1.textoDoPedido)(pedido))}`,
81
87
  `• Recomendo: ${(0, hitl_1.redigirSegredos)((0, hitl_contract_1.recomendacaoDoPedido)(pedido))}`,
82
- ...escolhas.flatMap((o, i) => [
83
- ` ${o.const}. ${o.title}`,
84
- ...(v2 ? [` → ${(0, hitl_1.redigirSegredos)(v2.alternativas[i].consequencia)}`] : []),
85
- ]),
88
+ ...escolhas.map(o => ` ${o.const}. ${o.title}`),
86
89
  livre ? '• Resposta: escreva sua resposta; o texto será enviado literalmente.'
87
- : `• Resposta: selecione uma opção no diálogo. Se necessário, digite ${v2 ? 'a letra' : 'o número'}.`,
90
+ : '• Resposta: selecione uma opção no diálogo. Se necessário, digite o número.',
88
91
  ...(livre && escolhas.length ? [' Exemplo: digitar “2” envia o texto “2”, sem selecionar a segunda opção.'] : []),
89
92
  `• Prazo: ${(0, horario_1.formatarPrazo)((0, hitl_contract_1.prazoDoPedido)(pedido), { rotulo: true, agora })}. Sem resposta, ${expiracao === 'esperar' ? 'aguardamos' : expiracao === 'seguir-recomendada' ? 'sigo com a recomendada' : 'o pedido será escalado'}.`,
90
93
  ];
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
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
4
  exports.resumirHitl = resumirHitl;
5
+ exports.linhaDosTecnicos = linhaDosTecnicos;
5
6
  exports.linhasDasOutrasMaquinas = linhasDasOutrasMaquinas;
6
7
  exports.linhasDasDecisoes = linhasDasDecisoes;
7
8
  exports.linhaDasPerguntas = linhaDasPerguntas;
@@ -41,7 +42,10 @@ exports.PERGUNTA_DO_RESUMO = 'Posso te mandar as perguntas agora?';
41
42
  exports.TETO_DE_THREADS_NOMEADAS = 5;
42
43
  function resumirHitl(itens, opcoes) {
43
44
  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
+ // RM-048 (D6): o que e do dono e contado para ele; o que e tecnico e dito a parte.
46
+ const doDono = itens.filter(i => (0, hitl_classificacao_1.quemDecide)(i.motivo) === 'dono');
47
+ const tecnicos = itens.filter(i => (0, hitl_classificacao_1.quemDecide)(i.motivo) === 'orquestrador');
48
+ const contagem = (0, hitl_classificacao_1.contarClassificacoes)(doDono, { quando: opcoes.quando, janelaMin, atoDoItem: opcoes.atoDoItem });
45
49
  const prontas = opcoes.prontas ?? 0;
46
50
  return {
47
51
  contrato: exports.CONTRATO_RESUMO, consultadoEm: opcoes.quando, janelaMin, ...contagem,
@@ -49,8 +53,15 @@ function resumirHitl(itens, opcoes) {
49
53
  pergunta: prontas > 0 ? exports.PERGUNTA_DO_RESUMO : null,
50
54
  decisoes: [...(opcoes.decisoes ?? [])], acimaDoLimiar: [...(opcoes.acimaDoLimiar ?? [])],
51
55
  ...(opcoes.outrasMaquinas?.length ? { outrasMaquinas: opcoes.outrasMaquinas.map(m => ({ ...m, esperando: [...m.esperando] })) } : {}),
56
+ tecnicos: tecnicos.length,
57
+ threadsTecnicas: [...new Set(tecnicos.map(i => i.thread).filter((t) => !!t))].sort(),
52
58
  };
53
59
  }
60
+ /** RM-048 (D6): a linha do que e tecnico, igual nos dois canais. Nao pede nada ao dono. */
61
+ function linhaDosTecnicos(r) {
62
+ const nomes = r.threadsTecnicas.length ? ` (${nomearThreads(r.threadsTecnicas)})` : '';
63
+ return `Conosco, impedimento técnico: ${r.tecnicos}${nomes}. Não precisa de você.`;
64
+ }
54
65
  /** Quantas threads de outra maquina o resumo nomeia antes de contar o resto. */
55
66
  exports.TETO_DE_ESPERAS_POR_MAQUINA = 3;
56
67
  /**
@@ -91,10 +102,11 @@ function linhasDasDecisoes(r, marcador, recuo) {
91
102
  const mostradas = r.decisoes.slice(-exports.TETO_DE_DECISOES_DETALHADAS), sobra = r.decisoes.length - mostradas.length;
92
103
  return [
93
104
  ...(r.decisoes.length ? [`${marcador}Decidi sem te perguntar: ${r.decisoes.length}`] : []),
105
+ // RM-048 (item 4): duas linhas por decisao, efeito primeiro; o resto do detalhe esta no placar.
94
106
  ...mostradas.flatMap(d => [
95
107
  `${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)}.`,
108
+ `${recuo} porquê: ${uma(d.porque, 120)}. Para mudar: ${uma(d.comoMudar, 120)}; ` +
109
+ `reverter agora: ${uma(d.custoDeReverter.agora, 80)}; depois: ${uma(d.custoDeReverter.depois, 80)}.`,
98
110
  ]),
99
111
  ...(sobra > 0 ? [`${recuo}e mais ${sobra}: ork decisao placar <thread>`] : []),
100
112
  ...r.acimaDoLimiar.map(f => `${recuo}${marcador ? '⚠️ ' : '! '}${f.thread} ${f.fase} já tomou ${f.decididas} decisões sozinha, ` +
@@ -143,27 +155,35 @@ function linhaDeResposta(codigo) {
143
155
  ? [`Responda com ${codigo} a (sim) ou ${codigo} b (agora não).`]
144
156
  : ['Responda: a (sim) ou b (agora não).'];
145
157
  }
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];
158
+ /**
159
+ * RM-048 (item 4): a pergunta sai no ALTO, logo depois do titulo, com a linha de como responder.
160
+ * Em 27/09 ela ficou escondida no fim, depois de tres blocos de decisoes informadas. Sem pergunta,
161
+ * a frase honesta continua sendo a ultima linha.
162
+ */
163
+ function abertura(r, codigo, marca, resposta) {
164
+ return r.pergunta ? [`${marca}${r.pergunta}`, ...linhaDeResposta(codigo).map(l => `${resposta}${l}`), ''] : [];
165
+ }
166
+ function fecho(r) {
167
+ return r.pergunta ? [] : ['', exports.SEM_PERGUNTA_NO_RESUMO];
149
168
  }
150
169
  function textoTelegram(r, codigo) {
151
170
  const quando = (0, horario_1.formatarDataHoraRotulada)(r.consultadoEm, { agora: r.consultadoEm });
152
171
  const linhas = [
153
172
  `🔔 Orkastery, resumo de ${quando}`,
154
- '',
173
+ ...abertura(r, codigo, '❓ ', '↩️ '),
174
+ ...(r.pergunta ? [] : ['']),
155
175
  `Esperando você: ${r.total}`,
156
176
  `⏱ Urgentes: ${r.urgentes}`,
157
177
  `⛔ Travando o avanço: ${r.bloqueantes}${r.threadsBloqueadas.length ? `, em ${r.threadsBloqueadas.length} threads` : ''}`,
158
178
  ...(r.threadsBloqueadas.length ? [` ${nomearThreads(r.threadsBloqueadas)}`] : []),
159
179
  `🔒 Sem volta depois de feito: ${r.criticos}`,
160
180
  `❓ ${linhaDasPerguntas(r.prontas)}`,
181
+ ...(r.tecnicos ? [`🔧 ${linhaDosTecnicos(r)}`] : []),
161
182
  ...(r.consertos ? [`🔧 ${linhaDosConsertos(r.consertos)}`] : []),
162
183
  ...(r.acumuladas ? ['', `📥 ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
163
184
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '🧭 ', ' ')] : []),
164
185
  ...(r.outrasMaquinas?.length ? ['', ...linhasDasOutrasMaquinas(r, '🖥️ ', ' ')] : []),
165
- '',
166
- ...fecho(r, codigo),
186
+ ...fecho(r),
167
187
  ];
168
188
  return linhas.join('\n');
169
189
  }
@@ -181,18 +201,19 @@ function textoTerminal(r, codigo) {
181
201
  const regua = ` ${'-'.repeat(largura)} ${'-'.repeat(numero)}`;
182
202
  const linhas = [
183
203
  `Orkastery, resumo de ${quando}`,
184
- '',
204
+ ...abertura(r, codigo, '', ''),
205
+ ...(r.pergunta ? [] : ['']),
185
206
  ` ${'o que'.padEnd(largura)} ${'quantos'.padStart(numero)}`,
186
207
  regua,
187
208
  ...celulas.map(([nome, n]) => ` ${nome.padEnd(largura)} ${String(n).padStart(numero)}`),
188
209
  ...(r.threadsBloqueadas.length
189
210
  ? ['', ` threads travadas (${r.threadsBloqueadas.length}): ${nomearThreads(r.threadsBloqueadas)}`] : []),
211
+ ...(r.tecnicos ? ['', ` ${linhaDosTecnicos(r)}`] : []),
190
212
  ...(r.consertos ? ['', ` ${linhaDosConsertos(r.consertos)}`] : []),
191
213
  ...(r.acumuladas ? ['', ` ${linhaDoLoteGuardado(r.acumuladas)}`] : []),
192
214
  ...(r.decisoes.length || r.acimaDoLimiar.length ? ['', ...linhasDasDecisoes(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
193
215
  ...(r.outrasMaquinas?.length ? ['', ...linhasDasOutrasMaquinas(r, '', ' ').map(l => l.startsWith(' ') ? l : ` ${l}`)] : []),
194
- '',
195
- ...fecho(r, codigo),
216
+ ...fecho(r),
196
217
  ];
197
218
  return linhas.join('\n');
198
219
  }