paris-immersion 0.1.3 → 0.1.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/dist/index.js +1 -1
- package/dist/lib/relay-agent.js +73 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { serveCommand } from './commands/serve.js';
|
|
|
12
12
|
const program = new Command()
|
|
13
13
|
.name('paris')
|
|
14
14
|
.description('Paris Immersion — CLI do aluno (login, workspace, tarefas)')
|
|
15
|
-
.version('0.1.
|
|
15
|
+
.version('0.1.5');
|
|
16
16
|
program
|
|
17
17
|
.command('login')
|
|
18
18
|
.description('Autenticar com sua conta Paris (device-code OR --token direto)')
|
package/dist/lib/relay-agent.js
CHANGED
|
@@ -68,12 +68,44 @@ export async function startRelayAgent(opts) {
|
|
|
68
68
|
let closed = false;
|
|
69
69
|
let reconnect;
|
|
70
70
|
let killTimer;
|
|
71
|
+
// Tamanho atual do pty (spawnCoachPty começa 80x24). Usado pra detectar resize
|
|
72
|
+
// "no-op" (reconexão com mesma dimensão) e forçar o Claude a repintar.
|
|
73
|
+
let ptyCols = 80;
|
|
74
|
+
let ptyRows = 24;
|
|
75
|
+
// Coalescing da saída do pty: o Claude emite a tela em RAJADAS de muitos
|
|
76
|
+
// chunks pequenos. Cifrar/enviar cada um (1 AES-GCM + 1 frame WS por chunk)
|
|
77
|
+
// é caro e deixa a escrita lenta. Junta numa janela curta → 1 frame por
|
|
78
|
+
// rajada. ~12ms é imperceptível e corta o overhead drasticamente.
|
|
79
|
+
const COALESCE_MS = 12;
|
|
80
|
+
const MAX_OUT_BUF = 64 * 1024;
|
|
81
|
+
let outBuf = '';
|
|
82
|
+
let flushTimer;
|
|
83
|
+
const flushOut = () => {
|
|
84
|
+
flushTimer = undefined;
|
|
85
|
+
if (!outBuf)
|
|
86
|
+
return;
|
|
87
|
+
const data = outBuf;
|
|
88
|
+
outBuf = '';
|
|
89
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
90
|
+
try {
|
|
91
|
+
ws.send(seal(key, TYPE_DATA, Buffer.from(data, 'utf8')), { binary: true });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* ws caiu no meio */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
71
98
|
// Mantém o Claude vivo por uma janela após o viewer sair, pra que uma
|
|
72
99
|
// reconexão (ex.: Safari derruba o ws periodicamente) REATACHE na MESMA sessão
|
|
73
100
|
// em vez de reiniciar o curso. O resize que o viewer manda ao reconectar faz a
|
|
74
101
|
// TUI do Claude repintar a tela atual.
|
|
75
|
-
const PTY_GRACE_MS =
|
|
102
|
+
const PTY_GRACE_MS = 600_000; // 10min — aluno que sai no meio e volta reatacha na MESMA sessão
|
|
76
103
|
const killPty = () => {
|
|
104
|
+
if (flushTimer) {
|
|
105
|
+
clearTimeout(flushTimer);
|
|
106
|
+
flushTimer = undefined;
|
|
107
|
+
}
|
|
108
|
+
outBuf = '';
|
|
77
109
|
if (pty) {
|
|
78
110
|
try {
|
|
79
111
|
pty.kill();
|
|
@@ -95,17 +127,43 @@ export async function startRelayAgent(opts) {
|
|
|
95
127
|
return;
|
|
96
128
|
}
|
|
97
129
|
pty.onData((d) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
outBuf += d;
|
|
131
|
+
// Flush imediato se acumulou muito (evita um frame gigante); senão coalesce.
|
|
132
|
+
if (outBuf.length >= MAX_OUT_BUF) {
|
|
133
|
+
if (flushTimer) {
|
|
134
|
+
clearTimeout(flushTimer);
|
|
135
|
+
flushTimer = undefined;
|
|
104
136
|
}
|
|
137
|
+
flushOut();
|
|
138
|
+
}
|
|
139
|
+
else if (!flushTimer) {
|
|
140
|
+
flushTimer = setTimeout(flushOut, COALESCE_MS);
|
|
105
141
|
}
|
|
106
142
|
});
|
|
107
143
|
pty.onExit?.(() => killPty());
|
|
108
144
|
};
|
|
145
|
+
// Força o Claude a repintar a tela INTEIRA (não só os diffs). Necessário quando
|
|
146
|
+
// um viewer reconecta com um xterm em branco e manda o MESMO tamanho — sem
|
|
147
|
+
// mudança de tamanho não há SIGWINCH, então o Claude não redesenha e a tela
|
|
148
|
+
// fica vazia/quebrada. Um "nudge" (uma linha a menos e volta) gera o SIGWINCH.
|
|
149
|
+
const forceRepaint = () => {
|
|
150
|
+
if (!pty || ptyCols < 1 || ptyRows < 2)
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
pty.resize(ptyCols, ptyRows - 1);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
/* noop */
|
|
157
|
+
}
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
try {
|
|
160
|
+
pty?.resize(ptyCols, ptyRows);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
/* noop */
|
|
164
|
+
}
|
|
165
|
+
}, 80);
|
|
166
|
+
};
|
|
109
167
|
const connect = () => {
|
|
110
168
|
if (closed)
|
|
111
169
|
return;
|
|
@@ -148,13 +206,20 @@ export async function startRelayAgent(opts) {
|
|
|
148
206
|
}
|
|
149
207
|
else if (msg.type === TYPE_RESIZE) {
|
|
150
208
|
const [cols, rows] = msg.body.toString('utf8').split(',').map(Number);
|
|
151
|
-
if (Number.isFinite(cols) && Number.isFinite(rows)) {
|
|
209
|
+
if (Number.isFinite(cols) && Number.isFinite(rows) && cols > 0 && rows > 0) {
|
|
210
|
+
const same = cols === ptyCols && rows === ptyRows;
|
|
211
|
+
ptyCols = cols;
|
|
212
|
+
ptyRows = rows;
|
|
152
213
|
try {
|
|
153
214
|
pty.resize(cols, rows);
|
|
154
215
|
}
|
|
155
216
|
catch {
|
|
156
217
|
/* race de resize */
|
|
157
218
|
}
|
|
219
|
+
// Mesma dimensão (reconexão) não dispara SIGWINCH → o Claude não
|
|
220
|
+
// repinta e a tela fica em branco/quebrada. Força o repaint.
|
|
221
|
+
if (same)
|
|
222
|
+
forceRepaint();
|
|
158
223
|
}
|
|
159
224
|
}
|
|
160
225
|
});
|
package/package.json
CHANGED