Skip to Content
Guia do DesenvolvedorModo Daemon (Aprofundamento para Desenvolvedores)Limite do Sistema de Arquivos do Workspace

Limite do Sistema de Arquivos do Workspace

Visão geral

Rotas HTTP de arquivo do daemon e chamadas delegadas ACP readTextFile / writeTextFile passam pelo limite WorkspaceFileSystem (packages/cli/src/serve/fs/), que fornece:

  • Resolução de caminhos — canonicaliza caminhos e rejeita qualquer coisa que escape do workspace delimitado, inclusive via symlinks.
  • Controle de confiança — recusa escritas quando o workspace não é confiável (untrusted_workspace).
  • Política de tamanho e conteúdo — limite de leitura/snapshot completo (MAX_READ_BYTES = 256 KiB), janelas de texto grande limitadas tanto em saída quanto em custo de scan (MAX_TEXT_SCAN_BYTES = 8 MiB), limite de escrita (MAX_WRITE_BYTES = 5 MiB), detecção de binários.
  • Atomicidade — escreve e depois renomeia com preservação do modo alvo e 0o600 padrão para novos arquivos.
  • Auditoria — cada acesso/negação emite um evento estruturado para PermissionAuditRing / monitoramento.
  • Erros tipados — união fechada FsErrorKind mapeada para códigos HTTP.

As rotas HTTP de arquivo (GET /file, GET /file/bytes, POST /file/write, POST /file/edit, GET /list, GET /glob, GET /stat) usam esse limite e nunca recebem a exceção de mesmo host. No daemon em produção, chamadas ACP que permanecem delegadas alcançam o adaptador da bridge injetado; chamadores genéricos da bridge usam o WFS apenas quando injetam tal adaptador. Runtimes qwen serve em produção no mesmo host anunciam readTextFile: false, então todos os consumidores filhos de FileSystemService.readTextFile usam o serviço de filesystem regular da CLI. Escritas finais de conteúdo ACP writeTextFile permanecem delegadas: alvos de workspace usam o WFS, enquanto um marcador estrito de ferramenta embutida pode selecionar um escritor host equivalente para um caminho externo apenas em adaptadores de mesmo host criados pelo daemon. Consulte o design de escrita externa.

Essa fatia de capacidade de leitura de texto cobre read_file direto mais as pré-leituras compartilhadas usadas pelas operações de write, edit, notebook, sed e artifact:

  • Intencionalmente aceita o comportamento de leitura regular da CLI em vez das garantias do lado de leitura do WFS. O documento de design possui a lista exata do que é renunciado.
  • O mesmo documento registra o sentido limitado em que o caminho de leitura do adaptador retido “fail closed”; o design de escrita externa separado registra como a falha de escrita final aprovada é fechada.
  • read_file externo direto mantém as regras normais de permissão da CLI e telemetria principal de operações de arquivo.
  • Rotas HTTP de filesystem permanecem escopo de workspace, e o comportamento da ferramenta de descoberta do agente não muda com essa capacidade.
  • Ações auxiliares como criação de diretório pai e comandos shell são caminhos existentes separados, não cobertos por este limite.
  • qwen serve assume um principal de segurança na mesma máquina e mesmo UID e não é um sandbox de SO.

Responsabilidades

  • Resolver caminhos fornecidos pelo usuário em valores ResolvedPath identificados que o restante do limite pode usar com segurança.
  • Recusar caminhos fora do workspace delimitado (path_outside_workspace) e caminhos cujo alvo seja um symlink (symlink_escape).
  • Recusar leituras de snapshot completo acima de MAX_READ_BYTES, permitindo janelas explícitas com saída limitada a MAX_READ_BYTES e custo de scan limitado a MAX_TEXT_SCAN_BYTES; recusar escritas acima de MAX_WRITE_BYTES e arquivos binários (binary_file).
  • Recusar escritas/edições quando o workspace não é confiável (untrusted_workspace) — controlado por assertTrustedForIntent(trusted, intent).
  • Respeitar padrões .gitignore / .qwenignore via shouldIgnore.
  • Realizar escrita atômica com renomeação e preservação do modo alvo; novos arquivos usam 0o600 como padrão (0o666 & ~umask derivado do umask sob a política de modo de novos arquivos system).
  • Emitir eventos de auditoria fs.access / fs.denied em toda operação.
  • Mapear cada falha para um FsError com tipo e status HTTP; os manipuladores de rota os serializam uniformemente.

Arquitetura

Layout dos módulos

ArquivoPropósito
paths.tscanonicalizeWorkspace, resolveWithinWorkspace, hasSuspiciousPathPattern, ResolvedPath identificado, união Intent (read | write | list | stat | glob).
policy.tsMAX_READ_BYTES, MAX_TEXT_SCAN_BYTES, MAX_WRITE_BYTES, MAX_UPLOAD_BYTES, BINARY_PROBE_BYTES, assertTrustedForIntent, detectBinary, enforceReadBytesSize, enforceReadSize, enforceWriteSize, shouldIgnore.
audit.tsFS_ACCESS_EVENT_TYPE, FS_DENIED_EVENT_TYPE, createAuditPublisher, tipos de payload de auditoria.
errors.tsClasse FsError, isFsError, união FsErrorKind (14 tipos), união FsErrorStatus (400 / 403 / 404 / 409 / 413 / 422 / 500 / 503).
workspace-file-system.tscreateWorkspaceFileSystemFactory, WorkspaceFileSystem (o orquestrador que lê/escreve/lista), WriteMode, ContentHash, FsEntry, FsStat, ListOptions, GlobOptions, ReadTextOptions, ReadBytesOptions, WriteTextAtomicOptions.

Taxonomia FsErrorKind

TipoHTTP PadrãoSignificado
path_outside_workspace400Caminho resolvido está fora do workspace delimitado.
symlink_escape400Alvo é um symlink (rejeitado conforme a postura conservadora do PR 18 + PR 20).
path_not_found404ENOENT.
binary_file422Conteúdo detectado como binário em uma rota de texto, ou texto grande em uma codificação que a rota de texto não consegue decodificar.
file_too_large413Texto sem janela/snapshot completo acima de MAX_READ_BYTES, um offset de linha além de MAX_TEXT_SCAN_BYTES, ou uma escrita acima de MAX_WRITE_BYTES.
hash_mismatch409expectedSha256 de concorrência otimista falhou, ou o arquivo mudou durante uma leitura estável.
file_already_exists409mode: 'create' contra um arquivo existente.
text_not_found422A string de busca do POST /file/edit não foi encontrada no arquivo.
ambiguous_text_match422Múltiplas correspondências quando exatamente uma era necessária.
untrusted_workspace403Escrita tentada em um workspace não confiável.
permission_denied403EACCES / EPERM no nível do SO.
io_error503ENOSPC / EIO / EBUSY / ETXTBSY / ENAMETOOLONG / EMFILE / ENFILE. Distinto de permission_denied para que pipelines de monitoramento não disparem alertas para “disco cheio”.
internal_error500Erro não-errno que atinge o limite (TypeError, bug de programador).
parse_error400 / 422Erro de parse do corpo da requisição (400) ou violação de invariante de serviço (422).

BridgeFileSystem (o adaptador do lado ACP)

packages/acp-bridge/src/bridgeFileSystem.ts define:

interface BridgeFileSystem { readText(params: ReadTextFileRequest): Promise<ReadTextFileResponse>; writeText(params: WriteTextFileRequest): Promise<WriteTextFileResponse>; }

Este é o ponto de injeção para readTextFile / writeTextFile do ACP. Testes da bridge e chamadores embutidos do Mode A podem omiti-lo em BridgeOptions; BridgeClient cai de volta para seu proxy inline fs.readFile / fs.writeFile (preserva o comportamento pré-F1). O qwen serve em produção conecta BridgeFileSystem através de createBridgeFileSystemAdapter(fsFactory) (packages/cli/src/serve/bridge-file-system-adapter.ts) e define delegateReadTextFileToClient: false. Filhos compatíveis com a capacidade portanto leem texto localmente e delegam escritas finais de texto ACP. O adaptador retém sua implementação de leitura para que leituras delegadas inesperadas ou que violem a capacidade ainda encontrem o limite de workspace do WFS. Seu caminho externo de host-writer é desabilitado por padrão e selecionado apenas por proveniência versionada exata em adaptadores de mesmo host pertencentes ao daemon; bridges injetadas, registradores e factories de workspace, ACP genérico e HTTP retêm o limite ordinário.

Duas propriedades defensivas que o adaptador DEVE preservar (porque o proxy inline é completamente ignorado quando o adaptador é injetado):

  1. Rejeitar arquivos não regulares — soquetes / pipes / dispositivos de caractere / entradas procfs / sysfs podem transmitir dados ilimitados apesar de stats.size === 0. O caminho inline lança exceção com describeStatKind(stats) na mensagem.
  2. Evitar buffer ilimitado de arquivo completo. O fallback inline limita uma leitura em buffer a READ_FILE_SIZE_CAP = 100 MiB. O adaptador injetado aplica em vez disso o contrato mais estrito do WorkspaceFileSystem: snapshots completos param em 256 KiB, enquanto arquivos UTF-8 maiores exigem um limit finito e são transmitidos a partir de um handle limitado por inode com no máximo 256 KiB retornados. Não deve ler um log inteiro de 500 MB apenas para retornar { line: 1, limit: 10 }.

O adaptador vai além: usa WorkspaceFileSystem.writeTextOverwrite (primitiva do PR 18) para escritas atômicas com arquivo temporário e renomeação, preservação de modo, 0o600 padrão e rejeição de symlinks dentro de um bloqueio por caminho. Isso é uma divergência do proxy inline pré-F1 que resolvia symlinks e escrevia através deles até o alvo — agentes que dependiam de escrever através de dotfiles com symlink agora precisam endereçar o caminho resolvido diretamente.

Preservação de FsError pelo fio ACP

Quando o adaptador BridgeFileSystem lança um FsError (kind: 'untrusted_workspace' / 'symlink_escape' / 'file_too_large' / etc.), o caminho padrão do erro RPC do SDK ACP serializa apenas error.message como um genérico -32603 "Internal error"kind / status / hint são removidos. O cliente RPC do agente downstream teria que fazer match com regex na mensagem legível para despachar UI tipada (reautenticação vs seletor de arquivo vs dica de proxy).

BridgeClient.writeTextFile e BridgeClient.readTextFile instalam uma proteção fina (packages/acp-bridge/src/bridgeClient.ts) que captura lançamentos com formato de FsError e os relança como RequestError do ACP:

function isFsErrorShape(err: unknown): err is FsErrorShape { return ( err instanceof Error && err.name === 'FsError' && typeof (err as { kind?: unknown }).kind === 'string' ); } function preserveFsErrorOverAcp(err: unknown): never { if (isFsErrorShape(err)) { throw new RequestError(-32603, err.message, { errorKind: err.kind, ...(err.hint !== undefined ? { hint: err.hint } : {}), ...(err.status !== undefined ? { status: err.status } : {}), }); } throw err; }

O cliente RPC do agente agora recebe data.errorKind (o valor fechado de FsErrorKind) além dos opcionais data.hint e data.status, permitindo que consumidores do SDK usem o enum tipado em vez de regex na mensagem.

Duas observações de design:

  • Duck typing em vez de importFsError vive em packages/cli/src/serve/fs/errors.ts enquanto BridgeClient vive em packages/acp-bridge. Um import { FsError } direto inverteria a dependência. A verificação por duck typing (name === 'FsError' + kind: string) espelha o que mapDomainErrorToErrorKind (status.ts) já faz para TrustGateError / SkillError pelo mesmo motivo de empacotamento entre pacotes.
  • Código JSON-RPC permanece em -32603 — a bridge não consegue mapear confiavelmente FsError.kind para um formato de código de erro JSON-RPC, então o campo estruturado data carrega a informação semântica para consumidores do SDK. O código de status no fio (-32603 “internal error”) não muda; clientes roteiam com base em data.errorKind.

Controle de confiança

assertTrustedForIntent(trusted, intent) consome o booleano de confiança injetado pelo chamador; a camada de política não lê Config.isTrustedFolder() diretamente. Operações de leitura / listagem / stat / glob são sempre permitidas (confiança só é necessária para escritas). Intenções de escrita em workspaces não confiáveis lançam FsError('untrusted_workspace', ..., status: 403). O sinal de confiança flui via WorkspaceFileSystemFactoryDeps.trusted: booleanrunQwenServe passa true porque o operador iniciou o daemon contra um workspace que implicitamente confia; createServeApp (embutido diretamente sem runQwenServe) usa false como padrão e emite um aviso uma vez por processo (veja 02-serve-runtime.md).

Fluxo de Trabalho

Leitura

readText não pula nem rejeita leituras por causa de regras de ignorar. Ela lê o arquivo normalmente e registra a classificação de ignorar correspondente em meta.matchedIgnore. list e glob filtram resultados ignorados apenas quando includeIgnored não está habilitado.

Escrita

A escrita atômica com renomeação garante que um SIGKILL / OOM no meio da escrita NÃO deixe o alvo truncado. mode: 'create' aborta com file_already_exists no lstat; mode: 'overwrite' prossegue; expectedSha256 ativa concorrência otimista (hash_mismatch em caso de incompatibilidade).

POST /file/edit (substituição de texto único)

Adiciona dois modos de falha além da escrita:

  • text_not_found (422) — string de busca não encontrada no arquivo.
  • ambiguous_text_match (422) — múltiplas correspondências quando exatamente uma era necessária (contrato da rota).

Fan-out de auditoria

FS_ACCESS_EVENT_TYPE / FS_DENIED_EVENT_TYPE carregam contexto (ctx), caminho, intenção, resultado, errorKind?, bytesLidos/escritos, sha256?.

Estado e Ciclo de Vida

  • A fábrica é construída uma vez na inicialização do daemon (runQwenServeresolveBridgeFsFactory → adapter).
  • Cada requisição constrói um RequestContext e invoca o orquestrador da fábrica apenas para aquela chamada — nenhum estado de arquivo persistente entre chamadas.
  • Bloqueios por caminho existem apenas durante a operação de escrita (sem bloqueio entre chamadas; escritas concorrentes no mesmo caminho disputam o bloqueio e serializam).
  • O anel de auditoria é gerenciado por runQwenServe e compartilhado com o publicador de auditoria de permissões.

Dependências

  • @qwen-code/qwen-code-coreIgnore, isBinaryFile, Config.isTrustedFolder().
  • node:fs, node:path, node:crypto.
  • @qwen-code/acp-bridge — contrato BridgeFileSystem do lado ACP.
  • Rotas HTTP: packages/cli/src/serve/routes/workspace-file-read.ts, workspace-file-write.ts.

Configuração

OrigemParâmetroEfeito
WorkspaceFileSystemFactoryDeps.trusted: booleanEntrada do construtorSe escritas são permitidas; padrão true do runQwenServe, false do createServeApp (com aviso).
ConstanteMAX_READ_BYTES = 256 KiBLimite de snapshot completo e texto retornado; texto maior exige um argumento de janela explícito.
ConstanteMAX_TEXT_SCAN_BYTES = 8 MiBBytes que uma leitura de texto grande pode escanear para localizar um offset de linha; além disso, file_too_large.
ConstanteMAX_WRITE_BYTES = 5 MiBLimite de escrita; dimensionado abaixo de express.json({ limit: '10mb' }).
ConstanteMAX_UPLOAD_BYTES = 50 MiBLimite de upload binário para POST /file/upload; uploads nunca sobrescrevem e numeram automaticamente nomes ocupados.
ConstanteBINARY_PROBE_BYTES = 4096Tamanho da amostra para detecção binária baseada em conteúdo.
Tags de capacidadeworkspace_file_read, workspace_file_bytes, workspace_file_write, workspace_file_uploadVeja 11-capabilities-versioning.md.
Arquivos do workspace.gitignore, .qwenignoreCaminhos ignorados aparecem como ignored: true do shouldIgnore.

Advertências e Limitações Conhecidas

  • Symlinks são rejeitados, não seguidos. Isso é uma divergência do proxy inline BridgeClient.writeTextFile pré-F1 que resolvia symlinks. Agentes escrevendo através de dotfiles com symlink precisam endereçar o caminho resolvido diretamente.
  • io_error e permission_denied são distintos. Não os confunda. Pipelines de monitoramento usam errorKind para alertas — incluir ENOSPC em permission_denied dispararia alertas para problemas de df -h.
  • O modo padrão de novos arquivos é 0o600, não o padrão do umask. O argumento mode da syscall de escrita ignora o umask. Agentes não podem passar uma sobreposição de modo por escrita. Operadores que desejam que arquivos criados pelo agente sigam o umask do daemon podem optar por daemon com QWEN_SERVE_NEW_FILE_MODE=system (arquivos existentes ainda preservam seu modo); consulte 17-configuration.md.
  • createServeApp com trusted: false padrão rejeita silenciosamente escritas ACP com untrusted_workspace para embedders que não injetam um fsFactory ou bridge personalizados. Um aviso único em stderr é emitido na primeira vez; chamadores subsequentes não veem lembrete. Veja 02-serve-runtime.md.
  • Texto grande exige um argumento de janela explícito, qualquer um de line / limit / maxBytes. Uma leitura sem nenhum deles resulta em file_too_large, porque um chamador que acredita ter o arquivo inteiro pode escrevê-lo de volta truncado. Janelas fazem stream a partir de um handle limitado por inode e nunca retornam mais que MAX_READ_BYTES.
  • MAX_READ_BYTES limita o que uma leitura retorna; MAX_TEXT_SCAN_BYTES limita o que ela custa. Offsets de linha são resolvidos escaneando a partir do byte 0, então { line: 900_000_000, limit: 20 } retorna quase nada e ainda percorre o arquivo. Após 8 MiB de scan a leitura é recusada com file_too_large apontando para readBytes, que alcança qualquer offset em O(1).
  • Janelas transmitidas toleram appends, não truncamento. O caminho de snapshot completo pode exigir estabilidade byte a byte porque retorna o arquivo inteiro; uma janela de prefixo não pode, ou toda leitura de um log ativo falharia. O caminho transmitido afirma identidade de inode mais “não encolheu”, então appends passam e truncamento / substituição ainda são rejeitados. sizeBytes reporta o tamanho no open, descrevendo o snapshot do qual a janela foi cortada.
  • Leituras parciais grandes omitem o hash do arquivo completo. originalLineCount é omitido quando o stream para antes do EOF.
  • Paginação é por cursor de bytes, não por linha. Uma leitura que deixa conteúdo para trás retorna hasMore e, quando um byte offset é derivável, um nextCursor opaco. Retomar a partir dele é O(1); retomar por line re-escaneia a partir do byte 0 e é recusado além de MAX_TEXT_SCAN_BYTES. O cursor carrega {dev, ino, size}, então um arquivo substituído ou truncado gera hash_mismatch em vez de bytes do lugar errado, enquanto um append o mantém válido. Leituras de snapshot não-UTF-8 reportam hasMore mas sem cursor — seu texto decodificado é uma re-codificação UTF-8 cujos comprimentos não mapeiam de volta para offsets do arquivo.
  • Adaptador BridgeFileSystem DEVE replicar ambas as barreiras do proxy inline (recusa de arquivos não regulares + buffer/stream limitado). O caminho inline é completamente ignorado quando o adaptador é injetado.

Referências

  • packages/cli/src/serve/fs/index.ts (barrel)
  • packages/cli/src/serve/fs/paths.ts
  • packages/cli/src/serve/fs/policy.ts
  • packages/cli/src/serve/fs/errors.ts
  • packages/cli/src/serve/fs/audit.ts
  • packages/cli/src/serve/fs/workspace-file-system.ts
  • packages/cli/src/serve/bridge-file-system-adapter.ts
  • packages/acp-bridge/src/bridgeFileSystem.ts
  • Referência de rota HTTP: ../qwen-serve-protocol.md.
Last updated on