Repository navigation
fix(web-shell): scope artifact actions to owning workspace #8510
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
wenshao
merged 5 commits into
QwenLM:main
from
zjunothing:fix/issue-8494-workspace-artifact-actions
Aug 6, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
8aadba3
fix(web-shell): scope artifact actions to owning workspace
zjunothing 46a0b6c
fix(web-shell): preserve artifact authority in strict mode
zjunothing fd1f53e
fix(web-shell): address artifact workspace review
zjunothing ac1ad61
test(web-shell): cover missing file owner
zjunothing dd48950
fix(web-shell): keep open artifact tabs alive across pane and list gaps
zjunothing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
.qwen/e2e-tests/2026-08-04-secondary-artifact-workspace-ownership.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # Secondary artifact workspace ownership | ||
|
|
||
| Issue: https://github.com/QwenLM/qwen-code/issues/8494 | ||
|
|
||
| ## Baseline | ||
|
|
||
| - Global CLI: `qwen 0.21.3` | ||
| - `qwen serve --help` confirms repeated `--workspace` registration and Web | ||
| Shell serving are available. | ||
| - Current component behavior is captured failure-first: an artifact tab with | ||
| no owner uses root workspace actions, and scheduled-task detail omits the | ||
| secondary `workspaceId`. | ||
|
|
||
| ## Local setup | ||
|
|
||
| 1. Create isolated primary and secondary temporary workspaces. | ||
| 2. Put the same relative sentinel filename in both workspaces with different | ||
| contents. | ||
| 3. Create distinct durable scheduled-task fixtures for both runtimes. | ||
| 4. Start the built `dist/cli.js serve` on loopback with both `--workspace` | ||
| flags and the production Web Shell bundle. | ||
|
|
||
| ## Scenarios | ||
|
|
||
| ### Secondary file action | ||
|
|
||
| Open a secondary-session artifact and download/preview its sentinel. | ||
|
|
||
| Expected: | ||
|
|
||
| - The request targets `/workspaces/<secondary>/file...`. | ||
| - The returned bytes are the secondary sentinel, never the primary sentinel. | ||
|
|
||
| ### Secondary scheduled task | ||
|
|
||
| Open the secondary durable task, toggle it, edit it, and delete the test copy. | ||
|
|
||
| Expected: | ||
|
|
||
| - Every request targets `/workspaces/<secondary>/scheduled-tasks...`. | ||
| - The primary task and primary task file are unchanged. | ||
|
|
||
| ### Ownership loss | ||
|
|
||
| Keep an artifact tab open, then remove or mark the secondary workspace | ||
| unavailable in the capability fixture before a delayed read resolves. | ||
|
|
||
| Expected: | ||
|
|
||
| - The panel displays the localized workspace-unavailable state. | ||
| - The delayed response is ignored. | ||
| - No request is retried against the primary route. | ||
|
|
||
| ## Evidence | ||
|
|
||
| - Focused test output for resolver, turn outputs, artifact panel, nested | ||
| subagent, and app tab propagation. | ||
| - Production build/typecheck/lint output. | ||
| - Captured two-workspace HTTP route log and sentinel hashes. | ||
| - Browser screenshot of the secondary artifact panel and the fail-closed | ||
| ownership-loss state when the local browser harness can represent it. | ||
|
|
||
| ## Results (2026-08-04) | ||
|
|
||
| Result: PASS | ||
|
|
||
| ### Automated verification | ||
|
|
||
| - Failure-first run: 3 expected failures and 29 existing passes. The primary | ||
| sentinel leaked into an ownerless tab, the secondary qualified client was | ||
| unused, and scheduled-task list omitted `workspaceId`. | ||
| - Focused ownership suite: 413/413 tests passed across the six affected test | ||
| files. | ||
| - Full Web Shell suite: 167 files and 2,777 tests passed. | ||
| - Web Shell lint and TypeScript typecheck passed. | ||
| - Package Web Shell build and repository root production build passed. | ||
| - The changed files pass Prettier. The package-wide format check still reports | ||
| five unchanged baseline files: | ||
| `BranchPickerPopover.module.css`, `GitModePopover.module.css`, | ||
| `GitDialog.module.css`, `PlanExecutionView.module.css`, and `index.html`. | ||
|
|
||
| ### Production two-workspace verification | ||
|
|
||
| The built CLI served the copied production bundle with two trusted workspaces. | ||
| `GET /capabilities` advertised distinct primary and secondary runtime IDs, and | ||
| the served JavaScript contained the new stale-owner guard. | ||
|
|
||
| The same relative file was read through both routes: | ||
|
|
||
| - `GET /file?path=artifact-owner.txt` returned | ||
| `PRIMARY_WORKSPACE_SENTINEL_8494`, SHA-256 | ||
| `818d5f4f1fb9c7e3f9bdfd9a3ad39361c93a23ca3f3d0ba5e4a7c889b33b9127`. | ||
| - `GET /workspaces/<secondary-id>/file?path=artifact-owner.txt` returned | ||
| `SECONDARY_WORKSPACE_SENTINEL_8494`, SHA-256 | ||
| `0e9fac7909b16ff8b17014ea103ed5018c8b4e9ad21d2f95e128fef3a3544a12`. | ||
| - The secondary bytes route returned only the secondary sentinel bytes. | ||
|
|
||
| Durable task CRUD was exercised against isolated primary and secondary task | ||
| fixtures: | ||
|
|
||
| - Secondary list, update, and delete requests all used | ||
| `/workspaces/<secondary-id>/scheduled-tasks...`. | ||
| - The secondary update changed only the secondary name/enabled state. | ||
| - The primary task remained present, enabled, and unchanged after that update. | ||
| - Deleting the secondary task emptied only the secondary list; the primary | ||
| task was still present. Both test tasks were then removed. | ||
|
|
||
| The daemon request log independently recorded the qualified file, bytes, | ||
| scheduled-task POST/GET/PATCH/DELETE routes and their successful statuses. | ||
|
|
||
| ### Visual evidence | ||
|
|
||
| The in-app browser runtime reported no available browser instance, so a UI | ||
| screenshot could not be captured in this environment. No synthetic screenshot | ||
| was substituted. The production server returned the current Web Shell bundle | ||
| with HTTP 200, and the DOM behavior is covered by the focused and full suites | ||
| above. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Web Shell artifact workspace ownership | ||
|
|
||
| Status: implemented and verified | ||
|
|
||
| Issue: https://github.com/QwenLM/qwen-code/issues/8494 | ||
|
|
||
| ## Problem | ||
|
|
||
| The Web Shell has one app-level `DaemonWorkspaceProvider`. Its default | ||
| workspace actions target the primary workspace. Session providers can attach to | ||
| secondary workspaces, but artifact surfaces currently retain or fall back to | ||
| the app-level actions. A file preview, download, review, nested subagent | ||
| artifact, or scheduled-task mutation can therefore reach the primary runtime | ||
| even though the producing session belongs to a secondary workspace. | ||
|
|
||
| The unsafe fallback also survives ownership changes: action objects are stored | ||
| in right-panel tabs, so removing or distrusting a workspace does not invalidate | ||
| an already-open tab. | ||
|
|
||
| ## Ownership contract | ||
|
|
||
| The producing session owns every turn output. Its `workspaceCwd`, resolved by | ||
| the session connection, is the source of the owner claim. The current daemon | ||
| capabilities are the authority that accepts or rejects that claim. | ||
|
|
||
| A target is usable only when one of these cases holds: | ||
|
|
||
| 1. Exactly one advertised workspace has the same cwd and is trusted. | ||
| 2. For a legacy single-workspace daemon with no workspace list, the cwd exactly | ||
| matches `capabilities.workspaceCwd`. | ||
|
|
||
| Unknown, duplicate, untrusted, removed, or identity-mismatched targets fail | ||
| closed. They never fall back to the primary workspace. | ||
|
|
||
| ## Design | ||
|
|
||
| ### Resolve at use time | ||
|
|
||
| Turn-output requests and right-panel tabs carry immutable owner identity | ||
| (`workspaceCwd` and the advertised `workspaceId`) instead of long-lived action | ||
| objects. `ArtifactPanel` resolves that identity against current capabilities on | ||
| every render. This makes workspace removal, trust loss, and runtime replacement | ||
| invalidate open tabs immediately. | ||
|
|
||
| The resolver returns a small artifact action surface only: | ||
|
|
||
| - `readWorkspaceFile` | ||
| - `readFileBytes` | ||
| - `stat` | ||
| - scheduled-task list/update/delete operations | ||
|
|
||
| Primary targets reuse the provider's primary actions. Trusted secondary | ||
| targets use `client.workspaceByCwd(cwd)` for file operations. Scheduled-task | ||
| operations still use the Web UI REST actions, but always receive the resolved | ||
| workspace id explicitly. | ||
|
|
||
| ### Propagation | ||
|
|
||
| - `TurnOutputs` derives the owner from its session `workspaceCwd`, uses the | ||
| scoped file actions for direct downloads, and stamps owner identity onto | ||
| review, artifact, and scheduled-task open requests. | ||
| - `ChatPane` and `SubagentDetail` preserve the request identity and add only the | ||
| producing session id. They no longer stamp root workspace actions onto the | ||
| request or artifact snapshot. | ||
| - `App` stores owner identity on each right-panel tab. Pane artifact snapshots | ||
| contain artifacts only; they no longer retain workspace clients. | ||
| - `ArtifactPanel` renders the existing workspace-unavailable state when the | ||
| owner cannot be resolved or no longer matches. It never substitutes | ||
| `useWorkspaceActions()` for a missing owner. | ||
| - Durable scheduled-task snapshots carry `workspaceId`, and every list, | ||
| update, toggle, and delete request passes that id. | ||
|
|
||
| ### Async invalidation | ||
|
|
||
| Scoped action wrappers check that their captured owner is still current before | ||
| starting an operation and again after each response. Effects and scheduled-task | ||
| mutations also ignore results after unmount or owner replacement. A response | ||
| started for one runtime cannot populate a tab after that runtime is removed or | ||
| replaced. | ||
|
|
||
| ## Non-goals | ||
|
|
||
| - No daemon route or workspace-registry semantics change. | ||
| - No primary fallback for compatibility on multi-workspace daemons. | ||
| - No redesign of artifact storage, session attachment, or scheduled-task data. | ||
| - No attempt to make arbitrary app-level workspace actions session-scoped; | ||
| only the artifact surfaces in issue #8494 are changed. | ||
|
|
||
| ## Verification | ||
|
|
||
| 1. Failure-first component tests pin the current primary fallback and missing | ||
| scheduled-task workspace id. | ||
| 2. Resolver tests cover primary, trusted secondary, unknown, duplicate, | ||
| untrusted, removed, and runtime-replaced targets. | ||
| 3. Turn-output tests verify secondary downloads and open requests use the | ||
| secondary owner. | ||
| 4. Artifact-panel tests verify missing/stale ownership fails closed and all | ||
| durable scheduled-task operations include `workspaceId`. | ||
| 5. A production bundle is run against two registered local workspaces with | ||
| distinct sentinel files/tasks. Captured requests must use the secondary | ||
| workspace-qualified routes and leave the primary sentinels unchanged. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.