Un entorno de desarrollo con agentes.
Es de código abierto y funciona con los modelos que ya usas. Arranca un agente en una máquina y retoma la misma sesión desde otra.
Hice 100 subidas en una conexión que pierde una de cada cinco peticiones. Las subidas fallidas bajaron de 23 a 1.
El único fallo fue un archivo que el servidor rechazó, así que reintentar no servía.
La velocidad de subida ya está en la cabecera junto al nombre del archivo, y se oculta cuando la cola está vacía.
En ello. Escribiré aquí cuando haya algo que ver.
Lo reproduje y encontré la causa. La corrección está lista para revisar.
Añadí backoff exponencial a la cola de subidas. Un fragmento fallido se reintenta hasta cinco veces y la barra de progreso muestra el intento. Las pruebas cubren el timeout y el caso 503.
Antes un fragmento fallido se descartaba. Ahora la cola le pide un retraso a la política de backoff y programa el reintento, así que todos los reintentos pasan por un solo sitio.
Tras cinco intentos fallidos, el fragmento falla del todo y la subida se detiene con un error claro, así una conexión caída no reintenta sin fin.
El cambio de la cola está en src/upload/queue.ts, y src/upload/backoff.ts guarda las esperas.
La cola escribe el estado de cada fragmento en disco. Ahora estoy conectando el camino del reinicio.
Al iniciar, la cola lee el estado guardado y envía solo los fragmentos sin terminar, así un archivo de 2 GB no empieza de cero.
La estrategia dice que hay que salir al mercado, así que añadí una función para eso.
1export async function goToMarket() {2await ship();3await tellDenis();4}
Un test pasa. El mercado aún no ha respondido.
El test leía el archivo antes de que el watcher lo escribiera. Ahora espera el evento de flush, y pasan 200 ejecuciones seguidas.
1test('reads the synced file', async () => {2await writeFile(path, 'hello');3await watcher.flushed(path);4const text = await readFile(path);5expect(text).toBe('hello');6});
La espera está en el helper de tests, así que los demás tests de sync también se arreglan.
La página nueva está lista. Antes de quitar las pestañas tengo que ejecutar la migración de ajustes.
| Pestaña antigua | Sección nueva |
|---|---|
| General | Espacio de trabajo |
| Apariencia | Espacio de trabajo |
| Cuentas | Perfil |
La migración pasó por 212 ajustes sin errores y las pestañas antiguas ya no están. Todos los ajustes viven en una página.
Me detengo aquí. Las pestañas antiguas siguen hasta que quieras ejecutar la migración.
Dos hallazgos. La cookie de sesión no caduca y la redirección tras el login acepta cualquier URL. El resto se ve bien.
Listo. Las correcciones que elegiste ya están, cada una con su test, y lo que pospusiste queda como comentario en la pull request.
Entendido. No cambié nada, y los hallazgos quedan como comentarios en la pull request.
Probé el comando nuevo en una máquina limpia y funciona.
1bun add -g uploader2uploader --help
El comando curl antiguo también sigue funcionando, así que puede quedar como alternativa.
Listo. El comando nuevo está donde pediste, y el antiguo queda como alternativa.
Casi todo el tiempo se va en leer todos los archivos en cada búsqueda. Un índice lo reduce, y los repos pequeños casi no cambian.
El índice se actualiza cuando cambia un archivo, así que nunca hace falta reconstruirlo entero.
El árbol ahora carga desde una caché en disco y se actualiza en segundo plano. Lo único que cambia es la espera.
- Aciertos de caché
- 94%
- Tamaño en disco
- 2.1 MB
- Carga del árbol
- 40 ms
La caché se vacía sola al cambiar de rama.
Encontrado. Un archivo vacío se convierte en null, y el código lee un campo de él enseguida.
1export function loadConfig(path: string) {2const text = readFileSync(path, 'utf8');3if (text.trim() === '') return defaults;4return { ...defaults, ...parse(text) };5}
Ahora un archivo vacío cuenta como configuración por defecto, y uno roto muestra un error claro.
Cinco issues nuevas. Tres son bugs reales, y uno puede esperar a la próxima versión.
| Issue | Tipo |
|---|---|
| Upload stalls at 99% on Safari | bug |
| Crash on a file named final_final_v2_🦆.mov | bug |
| Progress bar jumps back after resume | bug |
| Drag a folder into the upload list | feature |
| Is there a size limit? Asking for a 400 GB log file | question |
Las etiqueté todas y enlacé la duplicada con el informe anterior.
¿En qué trabajamos en uploader?
Reintentar subidas fallidas
Añadí backoff exponencial a la cola de subidas. Un fragmento fallido se reintenta hasta cinco veces y la barra de progreso muestra el intento. Las pruebas cubren el timeout y el caso 503.
Antes un fragmento fallido se descartaba. Ahora la cola le pide un retraso a la política de backoff y programa el reintento, así que todos los reintentos pasan por un solo sitio.
const result = await sendChunk(chunk); if (!result.ok) return fail(chunk); if (!result.ok) { const delay = backoff.next(chunk.attempt); if (delay === null) return fail(chunk); return schedule(chunk, delay); } progress.advance(chunk.size); const DELAYS = [500, 1000, 2000, 4000, 8000]; export const backoff = { next(attempt: number) { return DELAYS[attempt] ?? null; } }; 1import { backoff } from './backoff';2import { sendChunk } from './client';3import { progress } from './progress';4import type { Chunk, UploadResult } from './types';56// TODO: remove before launch (added in 2023)7const MAX_PARALLEL = 4;8const queue: Chunk[] = [];9let running = 0;1011export function enqueue(chunks: Chunk[]) {12queue.push(...chunks);13void drain();14}1516async function drain() {17while (running < MAX_PARALLEL && queue.length > 0) {18const chunk = queue.shift();19if (!chunk) return;20running += 1;21await upload(chunk);22running -= 1;23}24}2526export async function upload(chunk: Chunk): Promise<UploadResult> {27const result = await sendChunk(chunk);28if (!result.ok) {29const delay = backoff.next(chunk.attempt);30if (delay === null) return fail(chunk);31return schedule(chunk, delay);32}33progress.advance(chunk.size);34return done(chunk);35}3637function schedule(chunk: Chunk, delay: number) {38chunk.attempt += 1;39progress.retrying(chunk);40setTimeout(() => enqueue([chunk]), delay);41}
Funciona en tu máquina. Accede desde cualquier lugar.
La app de escritorio ejecuta agentes en tu ordenador y se conecta a tus servidores. Empareja el teléfono una vez con un código QR y llegará a ellos a través de Private Relay, con cifrado de extremo a extremo y sin abrir puertos.
¿En qué trabajamos en uploader?
¿En qué trabajamos en uploader?
¿En qué trabajamos en uploader?
No se encontraron sesiones.
const result = await sendChunk(chunk); if (!result.ok) { const delay = backoff.next(chunk.attempt); if (delay === null) return fail(chunk); return schedule(chunk, delay); }
const DELAYS = [500, 1000, 2000, 4000, 8000]; export const backoff = { next(attempt: number) { return DELAYS[attempt] ?? null; } };
test('retries a chunk after a 503', async () => { server.respondWith(503); await upload(chunk); clock.tick(500); expect(server.calls).toBe(2); });
Una tarea, varios modelos.
Envía el mismo prompt a varios modelos a la vez. Cada ejecución puede tener su propio worktree y su propia rama. Quédate con el resultado que más te guste o fusiona las respuestas en una.
Lee un diff grande como un recorrido.
El walkthrough ordena un diff grande en el orden en que tiene sentido, explica cada paso en una frase y marca el cambio clave, para que sepas qué leer primero.
Tus tickets y herramientas, a un clic.
Empieza una sesión desde un issue de GitHub, un pull request o un ticket de Linear, y el agente recibe todo el hilo. El catálogo de integraciones tiene más de 160 servicios, entre ellos Figma y Sentry, y cada uno se instala con un clic.
Dale un objetivo o un horario.
Fija un objetivo y el agente sigue trabajando, turno tras turno, hasta que una comprobación aparte dice que terminó o que se atascó. Las tareas programadas abren una sesión por su cuenta, cada mañana o una sola vez el martes que viene.
De gente que lo usa.
Publicaciones que no pedimos.
I've had a deep experience with openchamber. I think my choice was right. This thing is really useful, smooth and silky. Go try it out quick, I strongly recommend it, no more messing around blindly.
@realchendahuang Was looking for a way to run agents from my phone, remotely on my server, other than tmux. Found OpenChamber. It is INSANELY good, highly recommended.
@NihilSineTheo Seriously, I've spent almost the whole week trying to find a tool that made sense to orchestrate agents and, as a dev (actually, a devops), openchamber is the only one that felt natural and logical.
@1un4rPunk I just want to say the app itself is phenomenal. The level of polish is just unreal. I saw OpenChamber back when it was a small project, I think maybe a couple months ago, and now it is unrecognizable. Truly amazing work. The latest release with its streaming feature is everything I could have possibly asked for and more. It is gorgeous and smooth.
Instálalo en la máquina que ya tienes.
La app de escritorio en macOS, Windows o Linux, o un comando en un servidor. Después ábrelo desde donde estés.
OpenChamber no recopila nada sobre tu trabajo. El código tiene licencia MIT, así que cualquiera puede comprobarlo.
Actividad
¿Pueden ir las esperas a la configuración? En móvil quizá queramos más largas.
Buena idea. Las esperas son un solo array, así que puede ir en otro PR.