尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

qwen-code Web Shell 文件上传机制解析:从浏览器拖拽到工作区原子落盘的完整链路

qwen-code Web Shell 文件上传机制解析:从浏览器拖拽到工作区原子落盘的完整链路 qwen-code Web Shell 文件上传机制解析从浏览器拖拽到工作区原子落盘的完整链路【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 qwen-code 开源仓库的docs/design/web-shell-file-upload.md设计文档系统讲解 Web Shell终端中的 AI 编码智能体所附带的浏览器端交互界面如何把本地文件直接从浏览器上传到工作区包括 fs 层的writeBytesAtomic二进制原子写入原语、daemon 侧的POST /file/uploadREST 端点及其五段中间件链、SDK 的uploadWorkspaceFile()客户端方法以及 Web Shell 侧的拖拽上传与 面板上传两个入口。读完本文你将掌握上传永不覆盖、自动编号去重、并发受限、二进制原子落盘的完整设计脉络并能对照仓库源码定位每个环节的具体实现。一、问题背景为什么需要浏览器直接上传Web Shell 的 composer输入框支持通过path/to/file引用工作区文件但前提是该文件已经存在于工作区。用户经常需要把本地文件——截图、数据文件、配置文件——带入工作区并在 prompt 中引用。在引入本功能之前唯一的路径是先通过 CLI 或其他工具手动保存文件Web Shell 才能看见它。本功能为浏览器到工作区提供了直接上传通道包含三个入口能力拖放上传把文件拖到 composer 输入区上传到目标工作区根目录输入区上方显示内联进度条 面板上传项通过 文件选择器中的上传条目上传到当前浏览的目录自动插入引用上传完成后 composer 自动插入filename由既有的解析器继续消费受支持的文件。v1 明确不在范围内设计文档对边界做了清晰限定避免功能膨胀不支持 multipart 表单解析、分块/断点续传、文件夹上传不做expectedHash门控的 CAS 写入浏览器无法在上传前廉价地对大文件做哈希上传永不覆盖既有文件重名一律自动编号不提供 ACP-HTTP 对等端点_qwen/file/uploadv1 仅 REST大小限制为硬编码常量不提供 env/flag 配置。二、fs 层基础新增writeBytesAtomic与mkdir原语上传的是二进制字节而WorkspaceFileSystem源码此前的写能力全部面向文本writeTextAtomic/writeTextOverwrite/writeText/edit*这些方法都会做编码、BOM、行尾归一化会破坏二进制内容。因此第一步是给 fs 接口增加对称的二进制写方法writeBytesAtomic( p: ResolvedPath, data: Buffer, ): Promise{ sizeBytes: number; hash: ContentHash };该方法是单一用途、不覆盖no-clobber的创建原语语义与既有的writeTextAtomic({ mode: create })对齐新增MAX_UPLOAD_BYTES 50 * 1024 * 1024常量policy.ts经 fs/index.ts 导出。writeBytesAtomic内部执行enforceWriteSize(data.length, MAX_UPLOAD_BYTES)既有文本写继续使用默认的MAX_WRITE_BYTES 5 * 1024 * 1024。上传限制是独立的二进制入口策略不是对 agent 文本写上限的提高——两条策略互不干扰这一点在 policy.ts 的注释中明确强调信任边界由 fs 层自身强制assertTrustedForIntent(..., write)会抛untrusted_workspaceHTTP 层映射为 403HTTP admission 只是提前拒绝的优化手段代际守卫generation guard在方法入口、路径锁内临时文件发布前、以及最终发布检查点三处检查确保正在排空/已移除的 runtime 无法在准入后继续提交原子性临时文件 发布中断或取消的上传永远不会暴露残缺的目标文件目标已存在包括外部写入者竞态撞上最终发布抛FsError(file_already_exists)HTTP 409目标为符号链接时拒绝symlink_escape与文本写一致边界解析复用现有resolve(path, write)新文件以0o600权限创建而非 umask 默认值实现复用现有路径锁、临时文件预留、no-clobber 创建发布、代际守卫、审计与清理机制通用原子发布器被泛化为接受已校验的Buffer不复制第二份二进制专用原子写实现。字节路径不得经过atomicWriteTextResolvedFile因为其内部enforceWriteSize(buf.length)会误用 5 MiB 文本默认值——每个公开写路径都在调用共享发布器前用自己的策略校验最终字节缓冲。从源码看writeBytesAtomic的实现workspace-file-system.ts确实按上述契约执行入口generationGuard.assertOpen()→assertTrustedForIntent(..., write)→enforceWriteSize(data.length, MAX_UPLOAD_BYTES)→ 路径锁内assertCreateTargetAbsent→atomicPublishResolvedFile({ mode: create })最后记录审计事件。值得注意的细节是file_already_exists这种编号循环中的预期结果不会被记入审计的拒绝事件避免每个被跳过的占用名都产生一条fs.denied。fs 层还新增mkdir(p: ResolvedPath, opts?: { recursive?: boolean })实现用于让上传路由物化尚不存在的配置 drop 目录。它同样执行信任边界与代际守卫、持有路径锁、以0o755受 umask 影响创建目录并在每次mkdir后用lstat复查每个创建的组件、在下次mkdir前复查其父级——中途被替换为符号链接的组件会以symlink_escape拒绝而非跟随。目标已是目录则为 no-op目标是非目录或符号链接则拒绝。三、Daemon 侧POST /file/upload端点设计上传路由被扩展进routes/workspace-file-write.ts源码——该模块已拥有全部工作区文件变更路由及其私有的getFsFactory/parseClientId/resolveOriginatorClientId机制在此注册可避免把信任、身份、工作区解析逻辑复制到第二个模块。3.1 两个路由形态两者都在deps.mutate({ strict: true })之后POST /file/uploadlegacy 主工作区形态POST /workspaces/:workspace/file/uploadworkspace-qualified 形态路由所有权/作用域与POST /file/write完全一致工作区级、解析到具体 runtime。qualified 变体遵循同样的失败语义——未知含已移除工作区、非可信工作区、非活跃工作区一律拒绝永不回退到主 runtime。3.2 请求契约Content-Type: application/octet-stream X-Qwen-Client-Id: clientId Query parameters: path — 目标文件路径相对工作区根必填 由 URLSearchParams 编码文件名常含非 ASCII 服务端校验 Express 已解码的 req.query.path 不得再次调用 decodeURIComponent Body: 原始二进制字节3.3 五段中间件链从源码workspace-file-write.ts可以清晰看到两条路由共享同一中间件流水线deps.mutate({ strict: true })未认证的变更请求在缓冲任何 body 之前就被拒绝fileUploadAdmission在缓冲前完成所有廉价请求级检查——legacy 路由通过注入的isWorkspaceTrusted()校验主工作区当前可信qualified 路由执行resolveWorkspaceRuntimeFromParam→requireTrustedWorkspaceRuntime→setWorkspaceRouteContext未知/非可信/排空中的工作区在此终止强制Content-Type: application/octet-stream否则返回 415unsupported_media_type拒绝缺失/非法的path、超过MAX_UPLOAD_FILENAME_BYTES的 basenameparse_error信封目录形态的 path以/结尾、null 字节、可疑路径模式也在缓冲前静态拒绝若Content-Length存在且超过MAX_UPLOAD_BYTES立即返回上传专属 413raw parser 对 chunked body 与漏报/谎报 header 的客户端保持权威运行parseClientId与resolveOriginatorClientId非法 client id 在缓冲前拒绝将 path 拆为目录 basename以fs.resolve(dir, write)解析目录并fs.stat确认是既有目录父目录缺失时先用新增的WorkspaceFileSystem.mkdir(..., { recursive: true })创建上传到尚未存在的配置 drop 目录会连父级一并创建目录组件数上限MAX_UPLOAD_DIR_DEPTH 64防止单个请求在并发门之前物化无界目录树将 basename、已解析父目录、路由名、per-request fs 实例存入私有请求上下文handler 不再二次解析父目录。一个值得注意的设计取舍代码注释中明示admission 校验的是客户端写入的字面目录字符串候选名也从该字符串重新解析而非从规范化后的resolvedDir——因为resolve会对整个输入重跑hasSuspiciousPathPattern规范化段如工作区根自身路径、或docs - aux这类符号链接父级的 TARGET 名可能导致每个上传都失败。fileUploadConcurrencyGatelegacy 与 qualified 两条路由共享createServeApp创建的一个门最多同时准入MAX_CONCURRENT_UPLOADS 4个请求常量。饱和时返回 429upload_busy并带Retry-After: 1。门槽的释放分两个阶段handler 启动前由响应finish/close释放handler 启动后槽位保持到 handler 结束断开连接的客户端无法绕过内存上限fileUploadBodyParser包装express.raw({ type: application/octet-stream, limit: MAX_UPLOAD_BYTES, inflate: false })。fs 数值策略常量是 parser 与写入边界的单一事实来源——解析器按该上限缓冲的 body 绝不会在 fs 层因另一套上限被拒绝。回调拦截 body-parser 的status 413返回上传专属file_too_large信封避免全局 JSON parser 错误处理器误报既有的 10 MB JSON 上限415与客户端中止ECONNABORTED/req.aborted也有专门处理Handler将合法的零长度请求缺失 body 规范化为Buffer.alloc(0)取准入时的 fs 实例执行下述命名分配流程。3.4 命名分配上传永不覆盖WorkspaceFileSystem只暴露 no-clobber 字节创建上传专属命名策略归路由所有先试请求名遇file_already_exists再试编号候选在最终扩展名前插入(N)——report.pdf → report (1).pdf → report (2).pdf无扩展名README → README (1)点文件且无更多扩展则保持整体.env → .env (1)。循环最多 1000 次请求名 (1)至(999)常量NUMBERED_CANDIDATE_CAP全部占用则返回file_already_exists409每个编号候选都在捕获的已解析目录下构造并独立通过fs.resolve(candidate, write)。若解析结果与期望路径不同说明该名被工作区内符号链接占用路由继续编号且不调用writeBytesAtomic符号链接环ELOOP同样按占用处理边界逃逸与 I/O 错误终止循环文件名策略上限MAX_UPLOAD_FILENAME_BYTES 255常量用于规避常见 POSIX 文件系统的ENAMETOOLONG。加后缀超出上限时只在 Unicode 码点边界裁剪 stem直到stem suffix extension放下为止绝不裁剪扩展名、绝不拆分 UTF-8 序列若后缀扩展名本身都放不下则返回parse_error。splitStemExtension的实现特意把前导点.env视为 stem 的一部分而非扩展名分隔符。平台专属限制如 Windows 保留名仍由resolve/发布时的 fs 错误承担。3.5 响应与错误信封上传永远创建因此响应恒为201。path是服务端确认的最终路径——请求名被占用时为编号候选——客户端必须使用返回的 path而非请求的 path构造引用{ kind: file_upload, path: relative/path/to/report (1).pdf, sizeBytes: 12345, hash: sha256:64 lowercase hex }响应不含冗余的renamed布尔需要展示自动编号提示的客户端直接比较请求path与返回path即可。错误统一为{ errorKind, error, status, ...details }信封场景errorKindstatus编号候选耗尽file_already_exists409参数缺失/非法/超长文件名parse_error400错误 Content-Typeunsupported_media_type415路径逃逸 / 符号链接逃逸path_outside_workspace/symlink_escape400工作区不可信untrusted_workspace403权限不足permission_denied403body 过大上传专属file_too_large413上传专属 413 的完整信封admission 检查与路由级 raw-parser 包装都会发出因为 parser 失败发生在 handler 之前、无法走sendFsError{ errorKind: file_too_large, error: Request body too large (max 50 MiB), status: 413, maxBytes: 52428800 }并发门饱和时{ errorKind: upload_busy, error: Too many uploads in progress, status: 429, retryAfterSeconds: 1 }认证、client-id、工作区 runtime 类失败保留既有 daemon 信封——SDK 的DaemonHttpError已能保留其状态码与解析后的响应体因此本路由不为把code改名成errorKind而复制共享校验助手。3.6 限制与内存边界MAX_UPLOAD_BYTES50 MiB是 legacy 与 qualified 路由共享的硬编码策略常量没有字符串路由常量、也没有 env/flag 可配置性除非出现明确驱动。体量按截图、数据文件、配置文件设计单一数值常量同时约束 parser 与 fs 边界杜绝请求在一套上限下被完整缓冲、又在另一套上限下被拒绝内存上界分析express.raw会把完整 body 常驻内存仅靠监听器默认的 256 连接上限将允许约 12.5 GiB 的上传缓冲四槽共享门把上传 body 缓冲限制在约200 MiB加上正常框架开销。文档明确只有在生产测量显示需要不同吞吐/内存权衡时才应考虑把上限可配置化或用流式 fs 原语替换缓冲。3.7 能力发现与遥测在capabilities.ts新增workspace_file_upload: { since: v1 }标记实现——惯例是新路由契约 新标记与workspace_file_bytes从workspace_file_read拆分的逻辑一致DaemonCapabilitiesLimits增加可选maxWorkspaceFileUploadBytes特性存在时通告MAX_UPLOAD_BYTESroutes/capabilities.tsWeb Shell 发送前先查该值仅在能力兼容的 daemon 缺省时才回退 50 MiB。旧 daemon 因无特性标记而隐藏入口直接调用返回 404secondary-workspace 目标额外要求workspace_qualified_rest_core该能力的路由描述需更新以包含文件上传遥测在server/telemetry.ts的 POST allowlist 增加/workspace/file/upload后缀由/workspaces/:workspace/file/upload归一化而来紧挨既有/workspace/file/write否则延迟会落入 unknown 桶。四、SDKuploadWorkspaceFile()双客户端方法SDK 遵循既有请求对象签名风格参照writeWorkspaceFile(req, clientId?)。qualified 访问走既有client.workspaceById()/workspaceByCwd()选择器——不在DaemonClient上新增uploadWorkspaceQualifiedFile。interface DaemonWorkspaceFileUploadRequest { path: string; data: ArrayBuffer | Uint8Array | Blob; signal?: AbortSignal; /** 省略则继承客户端默认超时0 表示禁用超时。 */ timeoutMs?: number; /** 仅浏览器请求进度而不用 XMLHttpRequest 属于错误。 */ onProgress?: (event: { loaded: number; total: number }) void; } interface DaemonWorkspaceFileUploadResult { kind: file_upload; path: string; sizeBytes: number; hash: DaemonContentHash; } // DaemonClientlegacy-primary镜像 writeWorkspaceFile async uploadWorkspaceFile( req: DaemonWorkspaceFileUploadRequest, clientId?: string, ): PromiseDaemonWorkspaceFileUploadResult; // WorkspaceDaemonClientworkspace-qualified镜像其 writeWorkspaceFile async uploadWorkspaceFile( req: DaemonWorkspaceFileUploadRequest, clientId?: string, ): PromiseDaemonWorkspaceFileUploadResult;两个方法委托给DaemonClient上一个共享的 raw-POST 内部助手以 URL 路由名参数化与WorkspaceDaemonClient对/file/write→/workspaces/:workspace/file/write的配对方式相同认证头、超时/中止组合、响应解析与DaemonHttpError构造集中在一处。URL 用URL.searchParams.set(path, req.path)构造不要预先encodeURIComponent。传输层DaemonClient.ts 可查证提供onProgress时用XMLHttpRequestfetch不暴露上传进度否则用普通fetch。onProgress明确仅限浏览器——XMLHttpRequest不可用时不发送、直接失败绝不静默丢失进度。两条路径都遵循signal、使用相同认证/client-id 头与failOnError响应形态、应用timeoutMs。省略继承客户端既有超时0显式禁用。Web Shell 传timeoutMs: 0因为其 per-itemAbortController负责取消而一次合法的 50 MiB 上传可能超过 SDK 常规的 30 秒默认超时。五、Web Shell目标工作区解析与两个上传入口5.1 目标工作区解析Web Shell 是多工作区架构上传必须与 composer 既有文件操作使用相同目标不得新增第二套 voice 式解析器当useComposerCore携带workspace与atWorkspaceCwd时使用workspace.client.workspaceByCwd(atWorkspaceCwd).uploadWorkspaceFile(...)——与其 qualified 的listDirectory/globWorkspace动作完全一致包括经 qualified 路由寻址的主工作区仅没有atWorkspaceCwd的 legacy composer 路径使用workspace.client.uploadWorkspaceFile(...)现代多工作区 composer 缺少 cwd 属于不支持情形而非静默回退主工作区拖放与 面板入口共享同一选中客户端 面板额外提供该工作区内的目录legacy 目标要求workspace_file_upload能力cwd-qualified 目标要求workspace_file_upload与workspace_qualified_rest_core双能力且选中工作区必须在能力快照中出现且可信——否则两个上传入口都隐藏。5.2 宿主控制与上传目录fileUploadEnabledprop经 customization context 传递是附加闸门而非能力替代false强制隐藏工作区上传入口并禁用上传通道即使 daemon 通告了workspace_file_uploadtrue/省略仍要求能力与信任/qualified 路由检查。它永远不能绕过能力。附件拖拽/添加仍由attachmentsEnabled管辖见 web-shell-file-drop-action.md剪贴板粘贴图片/文本不受影响fileUploadDirectoryprop同样经 customization context 传递设置拖放文件的上传目录是不带前导/的相对路径uploads、uploads/images前导斜杠绝对路径会被 daemon 以越出工作区拒绝。省略或.则上传到工作区根。上传时 daemon 会创建不存在的目录含中间组件配置的 drop 文件夹无需手工创建。5.3useFileUploadhook新 hook 位于packages/web-shell/client/hooks/useFileUpload.tsinterface UseFileUploadOptions { /** 结构型客户端两个 daemon 客户端类都满足它。 */ client: FileUploadClient | undefined; maxBytes: number; targetKey: string; } interface FileUploadItem { id: string; file: File; targetPath: string; // 目标工作区中的请求相对路径 status: pending | uploading | done | error; progress: number; // 0–1 /** 本地判定的失败渲染层负责本地化文案。 */ errorCode?: tooLarge | noDaemon | tooManyFiles; error?: string; // 原始失败消息服务端错误 resultPath?: string; // 服务端确认的最终路径 /** 出现在 tooManyFiles 提示行上有多少文件未被排队。 */ skippedCount?: number; } interface UseFileUploadReturn { uploads: FileUploadItem[]; /** 任一条目 pending 或传输中为 true用于门控 composer 提交。 */ isBusy: boolean; uploadFiles: ( files: File[], targetDir: string, onUploaded?: (path: string) void, ) number; // 返回实际排队的文件数 removeUpload: (id: string) void; // 同时中止传输中的请求 }关键行为占用名一律自动编号安全边界失败、候选耗尽、I/O 失败产生错误行uploadFiles把onUploaded与每个排队条目一起存储每个成功上传恰好调用一次参数是服务端确认的最终路径单批最多MAX_FILES_PER_BATCH 100个文件常量溢出部分不入队以一条携带跳过数量的tooManyFiles提示行呈现避免无界拖放让严格串行队列长时间繁忙done 行显示最终文件名若resultPath ! targetPath额外展示简短自动编号提示调用方用与VoiceButton相同的workspace.capabilities?.features快照预检目标能力集不支持时隐藏入口排队前本地拒绝超过capabilities.limits.maxWorkspaceFileUploadBytes回退 50 MiB的文件并给出清晰错误服务端 413 仍是权威每个uploadFiles批次按选择顺序严格串行一个条目uploading其余pending失败或取消的条目不阻塞后续条目——这让浏览器/daemon 内存有界、插入顺序确定移除 pending/uploading 行会中止客户端请求原子写保证永不暴露残缺目标但取消是尽力而为——服务端已收到 body 并开始发布时完整文件仍可能落盘targetKey变化或 hook 卸载时中止并清空队列忽略上一代的迟到完成——为工作区 A 启动的上传绝不能把路径插入工作区 B 的 composer。5.4 composer 拖放流程在 composer 表面监听dragenter/dragover/dragleave/drop纯受支持图片的批次留在既有图片附件路径普通文件与混合批次走工作区上传——一次拖放绝不会被两条路径同时处理工作区上传批次取event.dataTransfer.files调用uploadFiles(files, fileUploadDirectory ?? ., onUploaded)——配置的上传目录默认目标工作区根daemon 在上传时创建缺失目录进度 UIcomposer 输入区上方一条细进度条每个排队/上传中/出错文件一行——文件名、状态或百分比、移除/取消操作状态文本不只靠颜色表达图标操作带本地化可访问名称完成行三秒后消失错误行保留至手动关闭完成时插入kind: file内联 composer 标签序列化值为finalPath经既有文件项同一管道转义escapeAtReferenceText(sanitizeInsertText(path))——带空格与非 ASCII 的截图文件名很常见。5.5 面板上传项在useAtMentionMenu.ts的createFileProvider中当文件 provider 处于目录浏览模式时在列表顶部前置一个kind: upload的合成AtMentionItem标签/描述使用既有 i18n 目录仅在入口查询为空时出现与显示currentDirectoryItem的条件相同不污染过滤结果参与常规键盘导航受既有ITEM_LIMIT切片约束选中它移除打开面板的提及文本、快照fileDirectoryRef.current、调用 composer 注入的onUploadRequest(targetDir, restoreQuery)回调并关闭菜单。若菜单打开期间上传能力消失陈旧条目accept 只关闭菜单、不移除文本。回调同步存储targetDir与当前targetKey保留restoreQuery然后点击挂载的隐藏input typefile multiple让浏览器视其为用户手势的一部分。这是 UI 行为而非工作区文件系统动作因此不属于AtMentionWorkspaceActions菜单 hook 保持与DaemonClient解耦input 的 change handler 仅在捕获的targetKey仍为当前值时才把选中文件上传到捕获的targetDir随后清空input.value以便重选同一文件能再次触发 change原生cancel监听器React 只在dialog上接cancel且该事件不冒泡调用存储的restoreQuery被取消的选择器把移除的提及文本还给用户成功时直接用服务端确认路径添加与既有文件菜单选择相同的内联文件标签。无需新缓存失效 API选中上传项会关闭菜单而close()已替换builtinCacheRef.current下次打开会获取全新目录列表。一个值得注意的边界上传到 git-ignored 路径会成功但在 列表中不可见entries.filter((entry) !entry.ignored)插入的引用仍然可解析。六、上传与消费的关系上传端点是格式无关的工作区存储。成功上传只保证字节在返回路径上被原子创建不保证每个模型/provider 都能内联或解读该文件。自动插入的引用继续走既有解析器并继承其限制图片使用既有图像管线及其源/解码限制PDF 使用既有 PDF 提取/渲染行为文本文件仍受模型上下文与文本处理限制约束不支持的二进制格式与超大的非图片二进制可能上传成功但在 prompt 尝试消费时失败。Web Shell 不做重复的文件嗅探也不维护第二套格式支持矩阵。用户可见文案只说文件已上传并引用不说每个模型都能读每种格式消费失败来自既有解析器。E2E 验证必须实际走一遍受支持文本文件与图片的 prompt 消费而不只是验证文件存在与插入的 composer 文本。七、端到端数据流Browser file ↓ (drag-drop or panel upload item) useFileUpload.uploadFiles() [target workspace resolved] ↓ (XHR with progress, or fetch) DaemonClient / WorkspaceDaemonClient.uploadWorkspaceFile() ↓ POST /file/upload?path... (raw octet-stream body) ↓ mutate gate → workspace/trust/client/metadata admission → concurrency gate → raw parser route candidate loop ↓ fs.resolve(candidate, write) → fs.writeBytesAtomic (no-clobber create) ↓ 201 with confirmed (possibly renumbered) path ↓ addTags([{ kind: file, serialized: finalPath }], { placement: inline })八、安全与失败行为汇总路由复用workspace-file-write.ts机制的严格变更门、工作区信任检查、客户端身份校验与fs.resolve边界守卫上传永不覆盖既有条目。占用名含工作区内最终组件符号链接一律自动编号绝不穿过既有条目写入本特性没有任何路径会修改或替换既有内容——候选循环只创建新文件。逃逸链接等安全边界失败、候选耗尽、I/O 失败仍是错误二进制写原子临时 发布网络失败与取消不会暴露残缺目标客户端迟到的取消仍可能使完整文件被发布上传不是幂等的服务端已发布但响应丢失时客户端无法得知创建是否成功Web Shell 不会在字节发送后自动重试手动重试可能有意创建编号副本错误 Content-Type → 缓冲前 415零字节application/octet-stream上传合法产生空缓冲的 SHA-256超大 body → 路由专属 413file_too_large信封handler/fs 失败走sendFsError路径逃逸或逃逸/竞态符号链接 → 400不可信工作区 → 403qualified 路由对未知含已移除、非可信、排空中的工作区永不回退主 runtime上传 body 内存受MAX_UPLOAD_BYTES × MAX_CONCURRENT_UPLOADS约束v1 常量下约 200 MiB认证、工作区解析、信任、Content-Type、Content-Length、元数据、客户端身份、初始路径边界解析全部先于并发门与 body 缓冲执行。九、实现顺序与测试验证设计文档给出了六步实施顺序fs 层MAX_UPLOAD_BYTES 泛化原子发布器 writeBytesAtomic→daemon 路由准入 四槽并发门 编号循环 错误信封 能力标记 遥测→SDK能力类型 双客户端uploadWorkspaceFile→useFileUploadhook可脱离 UI 测试的串行队列→composer 拖放→ 面板上传项。测试计划覆盖均可对照仓库中的 workspace-file-write.test.ts、workspace-qualified-rest.test.ts、workspace-file-system.test.ts、useFileUpload.test.tsx 与 DaemonClient.upload.test.ts 进一步深入fs 层二进制夹具字节级往返含空缓冲大于 5 MiB 且不超过MAX_UPLOAD_BYTES的载荷成功证明文本默认未作用于字节路径直接调用超过上限抛file_too_large既有文本写超MAX_WRITE_BYTES仍被拒不可信直接调用抛untrusted_workspace代际关闭后发布前无残留目标中断写无残缺目标no-clobber 发布竞态仍得file_already_exists符号链接目标被拒新文件0o600daemon 路由字节/哈希/大小正确零字节 201 空缓冲哈希错误 Content-Type 在缓冲前精确返回 415 信封大于 5 MiB 的合法上传成功超声明Content-Length立即拒绝chunked/漏报超限由 raw parser 拒绝同一 413 信封含errorKind/status/maxBytes绝无 10 MB 字样含空格/非 ASCII/%/#的路径恰好解码一次被文件/目录/工作区内符号链接占用 → 201 编号 path 且不穿过既有条目逃逸符号链接仍是边界错误编号保留扩展名、处理无扩展与点文件、跳过被占候选、长 Unicode stem 裁剪到 255 字节上限、1000 候选耗尽失败自动编号绝不修改请求目标同名并发落在不同候选四条路由形式并发可同时缓冲第五个精确收到 429upload_busyRetry-After断开/parser 错误释放槽位响应无派生的renamed标志能力标记与limits.maxWorkspaceFileUploadBytes被通告qualified 路由对非可信/未知/排空工作区在缓冲前拒绝且不回退SDK浏览器中进度回调触发无XMLHttpRequest时请求进度在发送前失败省略超时继承默认、0禁用、显式超时或 abort 信号取消请求fs 错误暴露errorKind其余 daemon 错误保留解析后 bodylegacy-primary 与 workspace-qualified 双客户端Web Shell hook/UI超限文件不发起 HTTP 即被拒超过 100 文件的批次排队前 100 并渲染一条携带跳过数的tooManyFiles提示行批次按选择顺序单请求执行失败/取消不阻塞后续移除 pending 项阻止其启动abort 后的迟到响应不调用onUploaded切换目标工作区中止并清空旧队列、忽略迟到完成每个成功最终路径恰好产生一个内联文件标签移除最后一个标签恢复占位符完成行三秒后消失纯受支持图片拖放留在图片附件路径普通文件/混合批次上传后不留 drag-active 样式Web Shell E2E拖放 → 输入区上方出现进度条 → 文件存在于工作区 → 内联文件标签出现覆盖含空格/非 ASCII/字面%的文件名请求名被占用含工作区内符号链接→ 以name (1).ext成功 自动编号提示 既有条目未动 标签用最终名批量拖放保持上传/标签顺序 面板嵌套目录浏览、选择上传、选中文件 → 触发被移除、捕获目录收到文件、内联标签出现重开菜单获取全新列表同一本地文件选两次触发两次上传能力缺失时两个入口均隐藏提交引用一个上传文本文件与一个上传图片的 prompt验证既有解析器供给内容不支持的/超大二进制呈现解析器既有的可读错误而非被描述为通用可消费。十、仓库源码速览若想深入源码核对本文论断推荐按以下路径阅读策略常量policy.tsMAX_UPLOAD_BYTES、MAX_WRITE_BYTES、assertTrustedForIntentfs 原语workspace-file-system.tswriteBytesAtomic与mkdir的实现及路径锁/代际守卫/审计复用上传路由workspace-file-write.tsadmission、并发门、raw parser、编号循环、双路由注册能力通告capabilities.ts 与 routes/capabilities.tsSDK 客户端DaemonClient.tsuploadWorkspaceFile与 XHR 进度路径Web Shell hookuseFileUpload.ts串行队列、批量上限、目标代际取消综上qwen-code 的 Web Shell 文件上传把浏览器本地文件 → 工作区原子落盘 →引用消费串成一条安全链路上传从不覆盖、重名自动编号、并发与内存双重有界、二进制写入原子且经过信任/代际/路径三重守卫——既保持了既有解析器的单一事实来源又为多工作区场景提供了与 composer 完全一致的目标解析语义。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表