Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
58da397
feat: add web shell workspace file uploads
Aug 10, 2026
28e6e54
Merge remote-tracking branch 'origin/main' into feat/web-shell-file-u…
Aug 10, 2026
5fc2836
Merge branch 'main' into feat/web-shell-file-upload
qwen-code-dev-bot Aug 10, 2026
ac32fb5
Merge branch 'main' into feat/web-shell-file-upload
qwen-code-dev-bot Aug 10, 2026
81f4898
Merge branch 'main' into feat/web-shell-file-upload
qwen-code-dev-bot Aug 10, 2026
b01325f
fix(serve): accept plain string targets in shared atomic publisher (#…
qwen-code-dev-bot Aug 10, 2026
dce6871
Merge branch 'main' into feat/web-shell-file-upload
wenshao Aug 10, 2026
54228b0
Merge branch 'main' into feat/web-shell-file-upload
qwen-code-dev-bot Aug 11, 2026
230d7ca
fix(review): bound web shell related paths
Aug 11, 2026
7229ed7
fix(review): address web shell file upload review findings (#8874)
qwen-code-dev-bot Aug 11, 2026
ee46078
Merge branch 'main' into feat/web-shell-file-upload
qwen-code-dev-bot Aug 11, 2026
fa410d1
test(serve): include upload capability in baseline
Aug 11, 2026
ffa1bc3
fix(review): pin workspace_file_upload in the serve capabilities inte…
qwen-code-dev-bot Aug 11, 2026
7a30c87
Merge branch 'feat/web-shell-file-upload' of https://github.com/QwenL…
qwen-code-dev-bot Aug 11, 2026
3b4ebc2
Merge remote-tracking branch 'origin/main' into feat/web-shell-file-u…
qwen-code-dev-bot Aug 11, 2026
cf4e038
fix(review): address round-2 web shell file upload review findings (#…
qwen-code-dev-bot Aug 11, 2026
b86e8eb
fix(review): address round-3 web shell file upload review findings (#…
qwen-code-dev-bot Aug 11, 2026
09776c5
fix(review): address round-4 web shell file upload review findings (#…
qwen-code-dev-bot Aug 12, 2026
91938eb
fix(review): address round-5 web shell file upload review findings (#…
qwen-code-dev-bot Aug 12, 2026
59d8d4d
fix(review): address remaining file upload findings (#8874)
Aug 12, 2026
72a89c1
fix(review): address round-6 web shell file upload review findings (#…
qwen-code-dev-bot Aug 13, 2026
2bf40fa
Merge remote-tracking branch 'refs/remotes/codex-tmp/main' into codex…
Aug 13, 2026
015f1b0
Merge remote-tracking branch 'origin/main' into feat/web-shell-file-u…
qwen-code-dev-bot Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 310 additions & 0 deletions docs/design/web-shell-file-upload.md

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions docs/developers/daemon/07-workspace-filesystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ That text-read capability slice covers direct `read_file` plus the shared pre-re
| File | Purpose |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paths.ts` | `canonicalizeWorkspace`, `resolveWithinWorkspace`, `hasSuspiciousPathPattern`, branded `ResolvedPath`, `Intent` union (`read \| write \| list \| stat \| glob`). |
| `policy.ts` | `MAX_READ_BYTES`, `MAX_TEXT_SCAN_BYTES`, `MAX_WRITE_BYTES`, `BINARY_PROBE_BYTES`, `assertTrustedForIntent`, `detectBinary`, `enforceReadBytesSize`, `enforceReadSize`, `enforceWriteSize`, `shouldIgnore`. |
| `policy.ts` | `MAX_READ_BYTES`, `MAX_TEXT_SCAN_BYTES`, `MAX_WRITE_BYTES`, `MAX_UPLOAD_BYTES`, `BINARY_PROBE_BYTES`, `assertTrustedForIntent`, `detectBinary`, `enforceReadBytesSize`, `enforceReadSize`, `enforceWriteSize`, `shouldIgnore`. |
| `audit.ts` | `FS_ACCESS_EVENT_TYPE`, `FS_DENIED_EVENT_TYPE`, `createAuditPublisher`, audit payload types. |
| `errors.ts` | `FsError` class, `isFsError`, `FsErrorKind` union (14 kinds), `FsErrorStatus` union (`400 / 403 / 404 / 409 / 413 / 422 / 500 / 503`). |
| `workspace-file-system.ts` | `createWorkspaceFileSystemFactory`, `WorkspaceFileSystem` (the orchestrator that reads/writes/lists), `WriteMode`, `ContentHash`, `FsEntry`, `FsStat`, `ListOptions`, `GlobOptions`, `ReadTextOptions`, `ReadBytesOptions`, `WriteTextAtomicOptions`. |
Expand Down Expand Up @@ -235,15 +235,16 @@ flowchart LR

## Configuration

| Source | Knob | Effect |
| ------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `WorkspaceFileSystemFactoryDeps.trusted: boolean` | Constructor input | Whether writes are allowed; defaults to `true` from `runQwenServe`, `false` from `createServeApp` (with warning). |
| Constant | `MAX_READ_BYTES = 256 KiB` | Full-snapshot and returned-text cap; larger text requires an explicit window argument. |
| Constant | `MAX_TEXT_SCAN_BYTES = 8 MiB` | Bytes a large-text read may scan to locate a line offset; past it, `file_too_large`. |
| Constant | `MAX_WRITE_BYTES = 5 MiB` | Write cap; sized below `express.json({ limit: '10mb' })`. |
| Constant | `BINARY_PROBE_BYTES = 4096` | Sample size for content-based binary detection. |
| Capability tags | `workspace_file_read`, `workspace_file_bytes`, `workspace_file_write` | See [`11-capabilities-versioning.md`](./11-capabilities-versioning.md). |
| Workspace files | `.gitignore`, `.qwenignore` | Ignored paths surface as `ignored: true` from `shouldIgnore`. |
| Source | Knob | Effect |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `WorkspaceFileSystemFactoryDeps.trusted: boolean` | Constructor input | Whether writes are allowed; defaults to `true` from `runQwenServe`, `false` from `createServeApp` (with warning). |
| Constant | `MAX_READ_BYTES = 256 KiB` | Full-snapshot and returned-text cap; larger text requires an explicit window argument. |
| Constant | `MAX_TEXT_SCAN_BYTES = 8 MiB` | Bytes a large-text read may scan to locate a line offset; past it, `file_too_large`. |
| Constant | `MAX_WRITE_BYTES = 5 MiB` | Write cap; sized below `express.json({ limit: '10mb' })`. |
| Constant | `MAX_UPLOAD_BYTES = 50 MiB` | Binary upload cap for `POST /file/upload`; uploads never overwrite and auto-number occupied names. |
| Constant | `BINARY_PROBE_BYTES = 4096` | Sample size for content-based binary detection. |
| Capability tags | `workspace_file_read`, `workspace_file_bytes`, `workspace_file_write`, `workspace_file_upload` | See [`11-capabilities-versioning.md`](./11-capabilities-versioning.md). |
| Workspace files | `.gitignore`, `.qwenignore` | Ignored paths surface as `ignored: true` from `shouldIgnore`. |

## Caveats & Known Limits

Expand Down
2 changes: 1 addition & 1 deletion docs/developers/daemon/11-capabilities-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Extension management: `extension_management_v2` adds the global `/extensions/*`

Workspace-qualified session reads: `workspace_persisted_transcript`, `workspace_session_export`, `workspace_archived_session_export`. The active and archived export tags are independent from each other and from `session_export` and `workspace_qualified_rest_core`, so clients must pre-flight the exact storage state they intend to export. Persisted transcript paging permits an untrusted secondary under its bounded read policy; both full export paths remain trusted-only.

Workspace mutation (Wave 4+): `workspace_memory`, `workspace_agents`, `workspace_agent_generate`, `workspace_acp_preheat`, `workspace_tool_toggle`, **`workspace_settings`** (conditional), `workspace_permissions`, `workspace_init`, `workspace_github_setup`, `workspace_trust`, `workspace_mcp_restart`, `workspace_mcp_manage`, `workspace_file_read`, `workspace_file_bytes`, `workspace_file_read_cursor`, `workspace_file_write`, **`workspace_reload`** (conditional).
Workspace mutation (Wave 4+): `workspace_memory`, `workspace_agents`, `workspace_agent_generate`, `workspace_acp_preheat`, `workspace_tool_toggle`, **`workspace_settings`** (conditional), `workspace_permissions`, `workspace_init`, `workspace_github_setup`, `workspace_trust`, `workspace_mcp_restart`, `workspace_mcp_manage`, `workspace_file_read`, `workspace_file_bytes`, `workspace_file_read_cursor`, `workspace_file_write`, `workspace_file_upload`, **`workspace_reload`** (conditional).

MCP guardrails: **`mcp_guardrails`** (`modes: ['warn', 'enforce']`), `mcp_guardrail_events`, `mcp_server_runtime_mutation`, **`mcp_workspace_pool`** (conditional), **`mcp_pool_restart`** (conditional).

Expand Down
8 changes: 7 additions & 1 deletion docs/developers/qwen-serve-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ registry. Clients **must** gate UI off `features`, not off `mode` (per design
'workspace_mcp_manage', 'mcp_guardrail_events',
'mcp_server_runtime_mutation',
'workspace_file_read', 'workspace_file_bytes', 'workspace_file_write',
'workspace_file_upload',
'session_approval_mode_control', 'workspace_tool_toggle', 'workspace_skill_toggle',
'workspace_skill_batch_toggle',
'workspace_settings', 'workspace_init', 'workspace_mcp_restart',
Expand Down Expand Up @@ -277,8 +278,13 @@ the hash-aware text mutation routes (`POST /file/write`, `POST /file/edit`).
The write tag means the route contract exists; it does not mean the current
deployment is open for anonymous mutation. Write/edit are strict mutation
routes and require a configured bearer token even on loopback.
`workspace_file_upload` covers `POST /file/upload`, the binary ingress route:
an `application/octet-stream` body capped at `MAX_UPLOAD_BYTES` (50 MiB) is
written into the workspace without ever overwriting — an occupied name is
auto-numbered (`name (1).ext`, `name (2).ext`, ...). It is also a strict
mutation route.

When `workspace_qualified_rest_core` is advertised, the same file surface is also available at `/workspaces/:workspace/file`, `/workspaces/:workspace/file/bytes`, `/workspaces/:workspace/stat`, `/workspaces/:workspace/list`, `/workspaces/:workspace/glob`, `/workspaces/:workspace/file/write`, and `/workspaces/:workspace/file/edit`.
When `workspace_qualified_rest_core` is advertised, the same file surface is also available at `/workspaces/:workspace/file`, `/workspaces/:workspace/file/bytes`, `/workspaces/:workspace/stat`, `/workspaces/:workspace/list`, `/workspaces/:workspace/glob`, `/workspaces/:workspace/file/write`, `/workspaces/:workspace/file/edit`, and `/workspaces/:workspace/file/upload`.

The same tag also exposes workspace-qualified project-agent CRUD at `/workspaces/:workspace/agents` and `/workspaces/:workspace/agents/:agentType`. These plural routes only read or mutate project-level agents for the selected workspace; `global` and `user` scope requests return `400 { code: "global_scope_not_supported_for_workspace_route" }`. Workspace-less `/workspace/agents` routes retain their existing primary-workspace behavior and remain the only REST surface for user-level agent scope.

Expand Down
1 change: 1 addition & 0 deletions integration-tests/cli/qwen-serve-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ describe('qwen serve — capabilities envelope', () => {
'workspace_file_bytes',
'workspace_file_read_cursor',
'workspace_file_write',
'workspace_file_upload',
Comment thread
ytahdn marked this conversation as resolved.
'session_approval_mode_control',
'workspace_tool_toggle',
'workspace_skill_toggle',
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/serve/bridge-file-system-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,7 @@ describe('createBridgeFileSystemAdapter', () => {
})),
edit: vi.fn(),
editAtomic: vi.fn(),
writeBytesAtomic: vi.fn(),
};
},
};
Expand Down Expand Up @@ -1030,6 +1031,7 @@ describe('createBridgeFileSystemAdapter', () => {
})),
edit: vi.fn(),
editAtomic: vi.fn(),
writeBytesAtomic: vi.fn(),
};
},
};
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/serve/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ export const SERVE_CAPABILITY_REGISTRY = {
// gate. Clients should still pre-flight `require_auth` separately for
// deployment posture; this tag only means the route contract exists.
workspace_file_write: { since: 'v1' },
// Daemon hosts binary file upload (`POST /file/upload`) behind the strict
// mutation gate. Uploads never overwrite; occupied names auto-number. New
// route contract = new tag (same split as `workspace_file_bytes` from
// `workspace_file_read`). The advertised upload byte cap is surfaced via
// `limits.maxWorkspaceFileUploadBytes`.
workspace_file_upload: { since: 'v1' },
Comment thread
ytahdn marked this conversation as resolved.
// Daemon hosts the session-level approval-mode
// control route `POST /session/:id/approval-mode` (gated by the
// mutation gate, strict). The route accepts `{mode, persist?}` —
Expand Down Expand Up @@ -345,8 +351,8 @@ export const SERVE_CAPABILITY_REGISTRY = {
scratch_workspace_registration: { since: 'v1' },
workspace_runtime_removal: { since: 'v1' },
// Workspace-qualified core REST routes under `/workspaces/:workspace/...`.
// Covers core file/status/permissions/trust/lifecycle/MCP/tool, memory,
// workspace agent CRUD, and persisted session organization surfaces.
// Covers core file read/write/upload, status/permissions/trust/lifecycle/MCP/tool,
// memory, workspace agent CRUD, and persisted session organization surfaces.
// Workspace-qualified settings also require the existing
// `workspace_settings` tag because that surface depends on settings
// persistence. ACP/WebSocket and auth stay outside this core tag;
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/serve/fs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
export {
MAX_READ_BYTES,
MAX_WRITE_BYTES,
MAX_UPLOAD_BYTES,
BINARY_PROBE_BYTES,
assertTrustedForIntent,
detectBinary,
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/serve/fs/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ export const MAX_TEXT_SCAN_BYTES = 8 * 1024 * 1024;
*/
export const MAX_WRITE_BYTES = 5 * 1024 * 1024;

/**
* Maximum bytes accepted by the binary upload write path
* (`writeBytesAtomic`). This is a distinct binary-ingress policy, NOT an
* increase to the agent text-write limit: text writes keep
* `MAX_WRITE_BYTES`. Sized for screenshots, data files, and configs that a
* user drags into the Web Shell. The daemon's upload route and the fs
* boundary share this single constant so a request buffered under the parser
* cap is never rejected later under a different limit.
*/
export const MAX_UPLOAD_BYTES = 50 * 1024 * 1024;

/**
* Sample size used for content-based binary detection. Aligned with
* `isBinaryFile` from `packages/core/src/utils/fileUtils.ts:414` so
Expand Down
Loading
Loading