我在每五个请求丢一个的连接上跑了 100 次上传。失败的上传从 23 次降到 1 次。
唯一的失败是服务器拒绝的文件,重试也帮不上忙。
上传速度现在显示在顶栏的文件名旁边,队列为空时会隐藏。
开始处理。有结果时我会在这里说。
已复现并找到原因。修复已可以审查。
为上传队列加入了指数退避。失败的分块最多重试五次,进度条会显示当前尝试次数。测试覆盖了超时和 503 两种情况。
以前失败的分块会被直接丢弃。现在队列会向退避策略询问延迟并安排重试,所有重试都经过同一个地方。
连续失败五次后,该分片判定为失败,上传会停止并给出明确的错误,所以断开的连接不会无休止地重试。
队列的改动在 src/upload/queue.ts,等待时长在 src/upload/backoff.ts。
队列现在会把每个分片的状态写入磁盘。我正在接上重启后的恢复流程。
启动时队列读取保存的状态,只发送未完成的分片,所以 2 GB 文件不用从头开始。
策略上写着要走向市场,所以我加了一个函数。
1export async function goToMarket() {2await ship();3await tellDenis();4}
一个测试通过了。市场还没有回复。
测试在监听器写入之前就读取了文件。现在它会等待 flush 事件,连续 200 次都通过了。
1test('reads the synced file', async () => {2await writeFile(path, 'hello');3await watcher.flushed(path);4const text = await readFile(path);5expect(text).toBe('hello');6});
等待逻辑放在测试辅助函数里,所以其他同步测试也一起修好了。
新页面已经完成。删除标签页之前,我需要先运行设置迁移。
| 旧标签页 | 新分区 |
|---|---|
| 常规 | 工作区 |
| 外观 | 工作区 |
| 账户 | 个人资料 |
迁移处理了 212 项设置,没有错误,旧标签页已移除。所有设置现在都在一个页面。
先停在这里。在你准备运行迁移之前,旧标签页会保留。
发现两个问题。会话 Cookie 没有过期时间,登录后的跳转接受任意 URL。其余没有问题。
完成了。你选择的修复都已加入,各带一个测试,推迟的部分作为评论留在拉取请求上。
明白了。没有做任何更改,发现的问题作为评论留在拉取请求上。
我在一台干净的机器上试过新命令,可以正常工作。
1bun add -g uploader2uploader --help
旧的 curl 命令也还能用,所以可以保留为备选。
完成了。新命令已放在你指定的位置,旧命令保留为备选。
大部分时间花在每次搜索都读取所有文件上。索引可以缩短这段时间,小仓库几乎不变。
索引会在文件变化时更新,所以无需完整重建。
文件树现在从磁盘缓存加载,并在后台刷新。变化的只有等待时间。
- 缓存命中
- 94%
- 磁盘占用
- 2.1 MB
- 文件树加载时间
- 40 ms
切换分支时缓存会自动清除。
找到了。空文件解析为 null,代码随即从中读取字段。
1export function loadConfig(path: string) {2const text = readFileSync(path, 'utf8');3if (text.trim() === '') return defaults;4return { ...defaults, ...parse(text) };5}
现在空文件按默认配置处理,损坏的文件会显示明确的错误。
有五个新 issue。三个是真实的 bug,其中一个可以等到下个版本。
| Issue | 类型 |
|---|---|
| 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 |
我都加了标签,并把重复项关联到较早的报告。
在 uploader 中做些什么?
重试失败的上传
为上传队列加入了指数退避。失败的分块最多重试五次,进度条会显示当前尝试次数。测试覆盖了超时和 503 两种情况。
以前失败的分块会被直接丢弃。现在队列会向退避策略询问延迟并安排重试,所有重试都经过同一个地方。
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}
运行在你的机器上,在任何地方都能连上。
桌面应用在你的电脑上运行智能体,也能连接你的服务器。手机扫一次二维码完成配对,之后通过 Private Relay 连接,端到端加密,无需开放端口。
在 uploader 中做些什么?
在 uploader 中做些什么?
在 uploader 中做些什么?
未找到会话。
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); });
一个任务,多个模型。
把同一个提示词同时发给多个模型。每次运行都可以有自己的工作树和分支。保留你最满意的结果,或者把多个回答融合成一个。
把大型 diff 读成一次导览。
Walkthrough 按照最容易理解的顺序重排大型 diff,每一步用一句话说明,并标出关键改动,让你知道先看哪里。
你的工单和工具,一键可达。
从 GitHub issue、拉取请求或 Linear 工单开始一个会话,智能体会拿到完整的讨论。集成目录里有 160 多个服务,包括 Figma 和 Sentry,每个都能一键安装。
给它一个目标,或一个计划。
设定一个目标,智能体会一轮接一轮地工作,直到另一个检查判定已完成或卡住。计划任务会自己启动会话,可以每天早上,也可以只在下周二运行一次。
来自用它的人。
我们没有请求过的帖子。
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.
装在你手头已有的机器上。
在 macOS、Windows 或 Linux 上用桌面应用,在服务器上只需一条命令。然后在你所在的任何地方打开它。
OpenChamber 不收集任何关于你工作的信息。代码采用 MIT 许可,任何人都可以查看核实。
动态
等待时长能放进配置吗?移动端可能需要更长。
好主意。等待时长现在是一个数组,可以放到后续 PR。