chatflux-gestao-agente 0.2.3 → 0.2.5
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 +14 -1
- package/lib/computador-estado.mjs +1 -0
- package/lib/computador-executor.mjs +18 -2
- package/lib/computador-portal.mjs +8 -1
- package/lib/configurar.mjs +11 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,10 +8,23 @@ 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.5 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
|
|
14
|
-
npx -y chatflux-gestao-agente@latest configurar
|
|
27
|
+
npx -y --prefer-online chatflux-gestao-agente@latest configurar
|
|
15
28
|
```
|
|
16
29
|
|
|
17
30
|
No Terminal do Mac ou PowerShell do Windows, esse comando instala a cópia
|
|
@@ -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');
|
|
@@ -53,6 +53,8 @@ async function officialClient(url, options) {
|
|
|
53
53
|
return new ConvexClient(url, options);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
57
|
+
|
|
56
58
|
export function createComputerPortal({ identity, readToken, clientFactory = officialClient, clock = {}, onReconnect = () => {} }) {
|
|
57
59
|
if (process.env.GESTAO_SERVICO) throw new Error('Modo serviço não pode operar um computador pessoal.');
|
|
58
60
|
scopeDirectory(identity);
|
|
@@ -109,7 +111,12 @@ export function createComputerPortal({ identity, readToken, clientFactory = offi
|
|
|
109
111
|
if (value.token === rejectedToken) throw error('RECONNECT_REQUIRED');
|
|
110
112
|
lastToken = value.token;
|
|
111
113
|
cancel(expiryTimer);
|
|
112
|
-
|
|
114
|
+
// setTimeout estoura acima de ~24,8 dias e dispara na hora (token de 1 ano): reagenda em passos.
|
|
115
|
+
const armExpiry = () => {
|
|
116
|
+
const delay = Math.max(0, value.expiresAt - now() - 60000);
|
|
117
|
+
expiryTimer = later(() => (delay > MAX_TIMER_MS ? armExpiry() : pause()), Math.min(delay, MAX_TIMER_MS));
|
|
118
|
+
};
|
|
119
|
+
armExpiry();
|
|
113
120
|
return value.token;
|
|
114
121
|
}
|
|
115
122
|
function scheduleAuth() {
|
package/lib/configurar.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Preparação pessoal,
|
|
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