paris-immersion 0.1.38 → 0.1.39
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.
|
@@ -11,6 +11,8 @@ import { loginCommand } from './login.js';
|
|
|
11
11
|
import { installCommand, ensureWorkspace } from './install.js';
|
|
12
12
|
import { locateWorkspace } from './repair.js';
|
|
13
13
|
import { maybeSelfUpdate } from '../lib/self-update.js';
|
|
14
|
+
import { restartDaemon } from '../lib/daemon.js';
|
|
15
|
+
import net from 'node:net';
|
|
14
16
|
const execFileAsync = promisify(execFile);
|
|
15
17
|
async function tokenOk() {
|
|
16
18
|
try {
|
|
@@ -45,6 +47,45 @@ async function workspaceForSlug(slug) {
|
|
|
45
47
|
}
|
|
46
48
|
return null;
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Religa o agente local (o `paris serve` que o botão do curso na War Room usa) e
|
|
52
|
+
* só aprova quando ELE disser que conectou no relay. Um agente preso com login
|
|
53
|
+
* antigo deixava a War Room em "Não achei o paris rodando" (D1 de outubro).
|
|
54
|
+
* Dentro do terminal embutido não religa: este terminal É o agente.
|
|
55
|
+
*/
|
|
56
|
+
async function ensureLocalAgent() {
|
|
57
|
+
if (process.env.PARIS_MCP_ENABLED === '1')
|
|
58
|
+
return { ok: true, detail: 'você já está no terminal do curso' };
|
|
59
|
+
const logFile = path.join(homedir(), '.paris', 'serve.log');
|
|
60
|
+
const offset = await fs.stat(logFile).then((s) => s.size).catch(() => 0);
|
|
61
|
+
try {
|
|
62
|
+
await restartDaemon();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return { ok: false, detail: `não consegui religar o serviço: ${err instanceof Error ? err.message : String(err)}` };
|
|
66
|
+
}
|
|
67
|
+
const portOpen = () => new Promise((resolve) => {
|
|
68
|
+
const s = net.connect(31000, '127.0.0.1');
|
|
69
|
+
s.once('connect', () => { s.destroy(); resolve(true); });
|
|
70
|
+
s.once('error', () => resolve(false));
|
|
71
|
+
s.setTimeout(1000, () => { s.destroy(); resolve(false); });
|
|
72
|
+
});
|
|
73
|
+
for (let i = 0; i < 40; i++) {
|
|
74
|
+
await new Promise((r) => setTimeout(r, 750));
|
|
75
|
+
const fresh = await fs
|
|
76
|
+
.readFile(logFile)
|
|
77
|
+
.then((b) => b.subarray(offset).toString('utf8'))
|
|
78
|
+
.catch(() => '');
|
|
79
|
+
if (/relay: conectado|relay ativo/.test(fresh))
|
|
80
|
+
return { ok: true, detail: 'conectado' };
|
|
81
|
+
if (/login expirado/.test(fresh))
|
|
82
|
+
return { ok: false, detail: 'o login do agente não vale — rode `paris logout` e `paris conectar`' };
|
|
83
|
+
}
|
|
84
|
+
// Sem relay configurado (dev local) o agente só escuta a porta local.
|
|
85
|
+
return (await portOpen())
|
|
86
|
+
? { ok: true, detail: 'rodando (sem relay)' }
|
|
87
|
+
: { ok: false, detail: 'o agente não subiu' };
|
|
88
|
+
}
|
|
48
89
|
async function runHookCheck(dir) {
|
|
49
90
|
const hook = path.join(dir, '.paris', 'hooks', 'paris-hook.mjs');
|
|
50
91
|
try {
|
|
@@ -137,7 +178,15 @@ export async function conectarCommand(opts = {}) {
|
|
|
137
178
|
return;
|
|
138
179
|
}
|
|
139
180
|
console.log(c.green(`✓ Envio ok (${r.sent} evento(s) enviados${r.pending ? `, ${r.pending} ainda na fila` : ''})`));
|
|
140
|
-
// 4.
|
|
181
|
+
// 4. Agente local do curso (botão "abrir o curso" da War Room)
|
|
182
|
+
const agent = await ensureLocalAgent();
|
|
183
|
+
if (!agent.ok) {
|
|
184
|
+
console.log(c.red(`✗ Agente do curso: ${agent.detail}. Chame um mentor técnico.`));
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
console.log(c.green(`✓ Agente do curso ${agent.detail}`));
|
|
189
|
+
// 5. Prova do lado do servidor
|
|
141
190
|
try {
|
|
142
191
|
const st = await apiAuthFetch('/api/activity/status/me');
|
|
143
192
|
const age = st.lastActivity ? Date.now() - new Date(st.lastActivity).getTime() : Infinity;
|
|
@@ -13,4 +13,8 @@ export declare function startRelayAgent(opts: {
|
|
|
13
13
|
loginToken: string;
|
|
14
14
|
cwd: string;
|
|
15
15
|
log?: (msg: string) => void;
|
|
16
|
+
/** Lê o token de login atual do disco (default: ~/.paris/credentials). Teste injeta. */
|
|
17
|
+
readLoginToken?: () => Promise<string | null>;
|
|
18
|
+
/** Intervalo entre releituras do login enquanto ele está inválido (default 15s). */
|
|
19
|
+
unauthRetryMs?: number;
|
|
16
20
|
}): Promise<RelayAgentHandle | null>;
|
package/dist/lib/relay-agent.js
CHANGED
|
@@ -39,6 +39,11 @@ function open(key, frame) {
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
async function defaultReadLoginToken() {
|
|
43
|
+
const { readCredentials } = await import('./credentials.js');
|
|
44
|
+
const creds = await readCredentials().catch(() => null);
|
|
45
|
+
return creds?.token ?? null;
|
|
46
|
+
}
|
|
42
47
|
/**
|
|
43
48
|
* Inicia o agente relay. Busca o serve-token + info do relay na API (Bearer
|
|
44
49
|
* creds.token), e se o relay estiver configurado, mantém a conexão wss e spawna
|
|
@@ -48,10 +53,28 @@ export async function startRelayAgent(opts) {
|
|
|
48
53
|
if (!opts.apiUrl || !opts.loginToken)
|
|
49
54
|
return null;
|
|
50
55
|
const log = opts.log ?? (() => { });
|
|
56
|
+
// O loginToken NÃO é fixo: o aluno pode relogar (`paris login`/`paris conectar`)
|
|
57
|
+
// com o daemon rodando. Antes o agente guardava o token do boot pra sempre e,
|
|
58
|
+
// depois de um re-login, ficava a noite inteira em "login expirado" até alguém
|
|
59
|
+
// rodar `paris restart` (visto no D1 de outubro, 06/10 07:59). Agora, a cada
|
|
60
|
+
// 'unauthorized', ele relê ~/.paris/credentials e troca pelo token novo.
|
|
61
|
+
let loginToken = opts.loginToken;
|
|
62
|
+
const reloadLoginToken = async () => {
|
|
63
|
+
try {
|
|
64
|
+
const creds = await (opts.readLoginToken ?? defaultReadLoginToken)();
|
|
65
|
+
if (creds && creds !== loginToken) {
|
|
66
|
+
loginToken = creds;
|
|
67
|
+
log('paris: login novo detectado — reconectando');
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* sem arquivo de credenciais: segue esperando */ }
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
51
74
|
const fetchInfo = async () => {
|
|
52
75
|
try {
|
|
53
76
|
const res = await fetch(`${opts.apiUrl.replace(/\/$/, '')}/api/onboarding/v2/serve-token`, {
|
|
54
|
-
headers: { authorization: `Bearer ${
|
|
77
|
+
headers: { authorization: `Bearer ${loginToken}` },
|
|
55
78
|
signal: AbortSignal.timeout(8000),
|
|
56
79
|
});
|
|
57
80
|
if (res.ok)
|
|
@@ -85,9 +108,15 @@ export async function startRelayAgent(opts) {
|
|
|
85
108
|
await sleep(5000);
|
|
86
109
|
initial = await fetchInfo();
|
|
87
110
|
}
|
|
88
|
-
|
|
111
|
+
// Login inválido no boot: em vez de desistir do relay (o agente ficava fora até
|
|
112
|
+
// um restart manual), espera o aluno relogar — relê o arquivo de login a cada
|
|
113
|
+
// `unauthRetryMs` e segue assim que ele mudar e a API aceitar.
|
|
114
|
+
const unauthRetryMs = opts.unauthRetryMs ?? 15_000;
|
|
115
|
+
while (initial.kind === 'unauthorized') {
|
|
89
116
|
log(RELOGIN_HINT);
|
|
90
|
-
|
|
117
|
+
await sleep(unauthRetryMs);
|
|
118
|
+
await reloadLoginToken();
|
|
119
|
+
initial = await fetchInfo(); // mesmo token pode passar (ex.: participante criado depois)
|
|
91
120
|
}
|
|
92
121
|
if (initial.kind !== 'ok' || !initial.info.relayUrl || !initial.info.token || !initial.info.e2eKeyB64)
|
|
93
122
|
return null;
|
|
@@ -255,8 +284,16 @@ export async function startRelayAgent(opts) {
|
|
|
255
284
|
// Token de login morto: NÃO re-disca com o velho (o relay 401-a e o loop
|
|
256
285
|
// nunca surfacing). Loga o caminho de re-login e agenda uma tentativa
|
|
257
286
|
// espaçada — se o aluno rodar `paris login`, a próxima busca passa e reata.
|
|
287
|
+
// Login trocado no disco (re-login com o daemon rodando)? Usa o novo e
|
|
288
|
+
// reconecta já. Senão espaça e loga o caminho de re-login.
|
|
289
|
+
if (await reloadLoginToken()) {
|
|
290
|
+
nextRetryMs = RETRY_FAST_MS;
|
|
291
|
+
if (!closed)
|
|
292
|
+
reconnect = setTimeout(connect, 0);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
258
295
|
log(RELOGIN_HINT);
|
|
259
|
-
nextRetryMs = RETRY_UNAUTH_MS;
|
|
296
|
+
nextRetryMs = opts.unauthRetryMs ?? RETRY_UNAUTH_MS;
|
|
260
297
|
if (!closed)
|
|
261
298
|
reconnect = setTimeout(connect, nextRetryMs);
|
|
262
299
|
return;
|
package/package.json
CHANGED