chatflux-gestao-agente 0.2.3 → 0.2.4

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/README.md CHANGED
@@ -8,6 +8,19 @@ faz na tela, e tudo no histórico com o seu nome.
8
8
  > nova, o que exige (e o que não exige) republicar, como testar sem browser, e as
9
9
  > armadilhas que já custaram tempo. Ele fica só no repo — não vai no npm.
10
10
 
11
+ ## Atualizar pelo portal
12
+
13
+ No aviso “Atualize o ChatFlux neste computador”, clique **Atualizar**. Com o
14
+ computador conectado e o programa 0.2.4 ou mais recente, o portal acompanha a
15
+ configuração completa, sem terminal. Se precisar renovar o login, o navegador
16
+ abre. Ao aparecer **Pronto ✓**, reinicie o VS Code; o aviso some sozinho.
17
+
18
+ Sem conexão, o botão mostra um pedido para copiar no Claude Code ou no Codex.
19
+ Cole, aprove quando a IA pedir e acompanhe: ela executa o comando abaixo. Em
20
+ programas antigos, o botão atualiza os plugins e mostra esse mesmo painel para
21
+ completar a preparação. “Ver detalhes” explica o motivo do aviso e “Agora não”
22
+ o dispensa nesta sessão.
23
+
11
24
  ## Instalar (equipe — um comando, sem clonar nada)
12
25
 
13
26
  ```sh
@@ -49,6 +49,7 @@ const privateResult = x => ({
49
49
  baixar_abrir: shape({ kind: oneOf('baixar_abrir'), canonicalPath: text, ...opening }),
50
50
  escolher_pasta_projeto: shape({ kind: oneOf('escolher_pasta_projeto'), canonicalPath: text, ...opening }),
51
51
  escolher_pasta_padrao: shape({ kind: oneOf('escolher_pasta_padrao'), canonicalPath: text }),
52
+ configurar: shape({ kind: oneOf('configurar'), items: x => Array.isArray(x) && x.length > 0 && x.length <= 20 && x.every(shape({ status: oneOf('ok', 'erro', 'aviso'), item: x => text(x) && x.length <= 100, text: x => text(x) && x.length <= 600 })), restartRequired: bool }),
52
53
  atualizar_plugin: shape({ kind: oneOf('atualizar_plugin'), claude: plugin, codex: plugin, restartRequired: bool }),
53
54
  responder_sessao: shape({ kind: oneOf('responder_sessao'), turnStatus: oneOf('encerrado', 'cancelado', 'timeout', 'incerto'), resposta: optional(x => typeof x === 'string' && x.length <= 4000), motivo: optional(oneOf('conversa_em_uso', 'editor_aberto', 'pasta_invalida', 'outro')) }),
54
55
  ler_historico: shape({ kind: oneOf('ler_historico'), count: x => uint(x) && x <= 30, partial: bool, digest }),
@@ -6,13 +6,14 @@
6
6
  * F02/Windows/Codex continuam atrás de capabilities e das receitas injetadas A4.
7
7
  */
8
8
  import path from 'node:path';
9
+ import { configurar } from './configurar.mjs';
9
10
  import { readFileSync } from 'node:fs';
10
11
  import { acquireInstanceLock } from './computador-estado.mjs';
11
12
  import { readUserJson } from './computador-identidade.mjs';
12
13
  import { createReplyAdapter } from './computador-resposta.mjs';
13
14
 
14
15
  const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url))).version;
15
- const ACTIONS = ['abrir_pasta', 'abrir_sessao', 'baixar_abrir', 'escolher_pasta_projeto', 'escolher_pasta_padrao', 'atualizar_plugin', 'responder_sessao', 'ler_historico'];
16
+ const ACTIONS = ['abrir_pasta', 'abrir_sessao', 'baixar_abrir', 'escolher_pasta_projeto', 'escolher_pasta_padrao', 'atualizar_plugin', 'configurar', 'responder_sessao', 'ler_historico'];
16
17
  const OPENING = ['abrindo_pasta', 'aguardando_editor', 'abrindo_conversa'];
17
18
  const RETRIES = [2000, 5000, 15000, 30000, 60000, 120000];
18
19
  const CLAIM = 'ops.computerRequestActions.claim', REPORT = 'ops.computerRequestActions.report';
@@ -48,7 +49,7 @@ function errorType(e) {
48
49
  return 'outro';
49
50
  }
50
51
 
51
- export async function runComputer({ identity, portal, journal, paths, folders, editor, git, plugins, diagnostics, history, reply = createReplyAdapter(), clock = {}, signal }) {
52
+ export async function runComputer({ identity, portal, journal, paths, folders, editor, git, plugins, diagnostics, history, configure = configurar, reply = createReplyAdapter(), clock = {}, signal }) {
52
53
  const now = clock.now?.bind(clock) ?? Date.now;
53
54
  const later = clock.setTimeout?.bind(clock) ?? setTimeout, cancel = clock.clearTimeout?.bind(clock) ?? clearTimeout;
54
55
  const random = clock.random?.bind(clock) ?? Math.random;
@@ -520,6 +521,21 @@ export async function runComputer({ identity, portal, journal, paths, folders, e
520
521
  }, () => ({ kind: 'concluir', privateResult: { kind: 'abrir_pasta' } }));
521
522
  return r;
522
523
  }
524
+ if (r.action === 'configurar') {
525
+ await phase('atualizando_plugin');
526
+ await effect('atualizando_plugin', () => configure(['--sim'], {
527
+ nonInteractive: true, signal: operationSignal, announce: () => {},
528
+ }), result => {
529
+ const short = (value, limit) => String(value ?? '').replace(/[\x00-\x1f\x7f]/g, ' ').trim().slice(0, limit);
530
+ const items = result.results.map(row => ({ status: row.ok === true ? 'ok' : row.ok === 'warn' ? 'aviso' : 'erro',
531
+ item: short(row.item, 100), text: short(row.message, 600) }));
532
+ const privateResult = { kind: 'configurar', items, restartRequired: result.restartRequired === true };
533
+ return items.some(row => row.status !== 'ok')
534
+ ? { kind: 'aguardar', choices: ['tentar_novamente', 'cancelar'], privateResult }
535
+ : { kind: 'concluir', privateResult };
536
+ });
537
+ return r;
538
+ }
523
539
  if (r.action === 'atualizar_plugin') {
524
540
  if (!capabilities.atualizarPlugin) throw fail('EDITOR_RECIPE_UNVERIFIED');
525
541
  await phase('atualizando_plugin');
@@ -1,4 +1,4 @@
1
- /** Preparação pessoal, sem comandos recebidos do portal. Efeitos só fora de --verificar. */
1
+ /** Preparação pessoal com receita fixa, também usada pelo botão do portal. Efeitos só fora de --verificar. */
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
@@ -29,6 +29,8 @@ async function ask(question) {
29
29
  export async function configurar(flags = [], options = {}) {
30
30
  if (flags.some(flag => !['--sim', '--verificar'].includes(flag))) throw new Error('Use gestao configurar [--sim] [--verificar].');
31
31
  const verify = flags.includes('--verificar'), yes = flags.includes('--sim');
32
+ const signal = options.signal;
33
+ signal?.throwIfAborted();
32
34
  const platform = options.platform ?? process.platform;
33
35
  if (!['darwin', 'win32'].includes(platform)) throw new Error('Configurar está disponível para Mac e Windows.');
34
36
  const p = platform === 'win32' ? path.win32 : path.posix;
@@ -43,6 +45,7 @@ export async function configurar(flags = [], options = {}) {
43
45
  const results = [];
44
46
  let restart = false;
45
47
  function result(item, ok, message) {
48
+ signal?.throwIfAborted();
46
49
  const line = `${item}: ${message} ${ok === 'warn' ? '⚠' : ok ? '✓' : '✗'}`;
47
50
  results.push({ item, ok, message }); announce(line);
48
51
  }
@@ -72,16 +75,20 @@ export async function configurar(flags = [], options = {}) {
72
75
  }
73
76
  const commands = {};
74
77
  function start(command, argv, spawnOptions = {}) {
78
+ signal?.throwIfAborted();
75
79
  if (!command) throw new Error('Programa não encontrado.');
76
80
  return spawnImpl(command.binary, [...command.prefix, ...argv], { ...spawnOptions, ...(command.cwd ? { cwd: command.cwd } : {}), env: commandEnv, shell: false, windowsHide: true });
77
81
  }
78
82
  async function run(command, argv, interactive = false) {
79
83
  return new Promise((resolve, reject) => {
80
84
  let child, timer, bytes = 0, stdout = '', stderr = '', done = false;
81
- const finish = (error, value) => { if (done) return; done = true; clearTimeout(timer); error ? reject(error) : resolve(value); };
85
+ const finish = (error, value) => { if (done) return; done = true; clearTimeout(timer); signal?.removeEventListener('abort', abort); error ? reject(error) : resolve(value); };
86
+ const abort = () => { try { child?.kill('SIGKILL'); } catch { /* somente filho próprio */ } finish(signal.reason ?? new Error('Cancelado.')); };
82
87
  const stop = () => { try { child?.kill('SIGKILL'); } catch { /* somente filho próprio */ } finish(new Error('O comando demorou demais; tente novamente.')); };
83
88
  try {
84
- child = start(command, argv, { stdio: interactive ? 'inherit' : ['ignore', 'pipe', 'pipe'] });
89
+ child = start(command, argv, { stdio: interactive && !options.nonInteractive ? 'inherit' : ['ignore', 'pipe', 'pipe'] });
90
+ signal?.addEventListener('abort', abort, { once: true });
91
+ if (signal?.aborted) { abort(); return; }
85
92
  for (const stream of ['stdout', 'stderr']) child[stream]?.on('data', chunk => {
86
93
  bytes += Buffer.byteLength(chunk); if (bytes > 1024 * 1024) return stop();
87
94
  if (stream === 'stdout') stdout += chunk; else stderr += chunk;
@@ -225,7 +232,7 @@ export async function configurar(flags = [], options = {}) {
225
232
  }
226
233
  } else {
227
234
  const bridge = (tool, argv, spawnOptions) => start(commands[tool], argv, spawnOptions);
228
- const updated = await updatePlugins({ platform, tools: ready, marketplaceUpdated: ready,
235
+ const updated = await updatePlugins({ platform, signal, tools: ready, marketplaceUpdated: ready,
229
236
  ...(platform === 'win32' ? { windowsAdapter: bridge } : { spawnImpl: (binary, argv, spawnOptions) => bridge(binary.endsWith('/claude') ? 'claude' : 'codex', argv, spawnOptions) }) });
230
237
  restart ||= updated.restartRequired;
231
238
  for (const tool of ['claude', 'codex']) if (commands[tool]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatflux-gestao-agente",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Opere o portal Gestão da ChatFlux pelo Claude Code, Claude Desktop ou qualquer harness MCP — com a sua identidade e o seu papel.",
6
6
  "keywords": [