Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9e417ad
docs(web-shell): design session source registration
callmeYe Sep 7, 2026
9a5e665
docs(web-shell): add Chinese session sources design
callmeYe Sep 7, 2026
a9be7a0
feat(web-shell): add unified session sources
callmeYe Sep 7, 2026
0b51688
Merge branch 'main' into codex/session-sources-design
wenshao Sep 7, 2026
63ec56e
Merge branch 'main' into codex/session-sources-design
wenshao Sep 7, 2026
a2b12d0
fix: complete session source lifecycle and UI contracts
callmeYe Sep 8, 2026
af2c846
Merge origin/main into codex/session-sources-design
callmeYe Sep 8, 2026
4bdfaba
fix(core): keep source registration in top-level sessions
callmeYe Sep 8, 2026
59bc323
fix(live-host): keep packaging in the standalone npm workspace
callmeYe Sep 8, 2026
55a882d
Merge branch 'main' into codex/session-sources-design
wenshao Sep 8, 2026
5955b50
fix(web-shell): capture reading anchors before admitting history pages
callmeYe Sep 8, 2026
10a542b
Merge remote branch into history anchor fix
callmeYe Sep 8, 2026
1301a8e
Merge main and reconcile daemon capability counts
callmeYe Sep 8, 2026
1673176
Merge main while preserving history anchors and column alignment
callmeYe Sep 8, 2026
b265680
chore(web-shell): merge main into unified session sources
qwen-code-dev-bot Sep 8, 2026
cfa059c
fix(web-shell): close source review gaps and restore regression coverage
callmeYe Sep 9, 2026
ba9a8f3
Merge main and preserve Sources in standalone startup
callmeYe Sep 9, 2026
c817df5
fix(web-shell): preserve standalone source links and close hidden dia…
callmeYe Sep 9, 2026
fe5ebb1
fix(web-shell): preview session-owned sources without workspace access
callmeYe Sep 9, 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
Binary file added docs/design/assets/session-sources-collapsed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/design/assets/session-sources-expanded.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
427 changes: 427 additions & 0 deletions docs/design/web-shell-session-sources.md

Large diffs are not rendered by default.

240 changes: 240 additions & 0 deletions docs/design/web-shell-session-sources.zh-CN.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/developers/qwen-serve-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ operator diagnostic snapshot documented below.
| `standalone_sessions_v1` | the daemon has installed the complete standalone-session runtime, lifecycle coordinator, durable deletion journal, managed-directory implementation, and `/standalone/sessions` route family. Direct embeds without the complete dependency graph omit both the routes and this tag. |
| `standalone_session_options_v1` | the complete standalone-session runtime is installed (same condition as `standalone_sessions_v1`), so the read-only, sessionless `GET /standalone/session-options` route is registered on the internal Conversations runtime. |
| `session_artifacts_persistence` | session artifact persistence is wired for the runtime. |
| `session_sources` | session source persistence is wired for the runtime. Registers metadata-only workspace files, uploaded attachments, and HTTP(S) links through the live session owner. |
| `session_generation` | session generation helpers are available. |
| `scheduled_task_session_reuse` | durable scheduled-task session management is active and every managed daemon runtime has installed the callback that lets a task explicitly bind to its current existing session. |
| `workspace_generation` | workspace-scoped generation helpers are available. |
Expand Down Expand Up @@ -2726,7 +2727,7 @@ ACP-over-HTTP uses the same request and response bodies through vendor methods `

### Multi-workspace live-session routing

When `multi_workspace_sessions` is advertised, live-session operations identify their workspace from the `sessionId`; clients do not add a workspace selector to the URL. In addition to the existing owner-routed lifecycle operations, this applies to `PATCH /session/:id/metadata`, `POST /session/:id/recap`, `POST /session/:id/generate`, `POST /session/:id/btw`, `POST /session/:id/mid-turn-message`, `GET /session/:id/mid-turn-messages`, `DELETE /session/:id/mid-turn-messages/:messageId`, `POST /session/:id/tasks/:taskId/cancel`, `POST /session/:id/goal/clear`, `POST /session/:id/continue`, `POST /session/:id/language`, `POST /session/:id/artifacts`, and `DELETE /session/:id/artifacts/:artifactId`. The daemon routes each request to the trusted runtime that owns the live session. An untrusted non-primary owner returns `403 untrusted_workspace`, a missing live owner returns `404 session_not_found`, and an ambiguous owner fails closed with `500 ambiguous_session_owner`.
When `multi_workspace_sessions` is advertised, live-session operations identify their workspace from the `sessionId`; clients do not add a workspace selector to the URL. In addition to the existing owner-routed lifecycle operations, this applies to `PATCH /session/:id/metadata`, `POST /session/:id/recap`, `POST /session/:id/generate`, `POST /session/:id/btw`, `POST /session/:id/mid-turn-message`, `GET /session/:id/mid-turn-messages`, `DELETE /session/:id/mid-turn-messages/:messageId`, `POST /session/:id/tasks/:taskId/cancel`, `POST /session/:id/goal/clear`, `POST /session/:id/continue`, `POST /session/:id/language`, `POST /session/:id/artifacts`, `DELETE /session/:id/artifacts/:artifactId`, `GET /session/:id/sources`, `POST /session/:id/sources`, and `DELETE /session/:id/sources/:sourceId`. The daemon routes each request to the trusted runtime that owns the live session. An untrusted non-primary owner returns `403 untrusted_workspace`, a missing live owner returns `404 session_not_found`, and an ambiguous owner fails closed with `500 ambiguous_session_owner`.

This rule is live-session-only and does not make every workspace-less session route multi-workspace-aware. Persisted or archived operations use their documented workspace-qualified routes. `POST /session/:id/branch`, `POST /session/:id/fork`, and `POST /session/:id/cd` intentionally remain primary-only and return `non_primary_session_route_not_supported` for non-primary owners.

Expand Down
29 changes: 29 additions & 0 deletions docs/plans/web-shell-session-sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Web Shell session sources implementation

The session source list follows the [design contract](../design/web-shell-session-sources.md). The feature is implemented on `codex/session-sources-design` for local acceptance of PR #11262.

## Implementation

The core owns one durable source service per daemon session. Both `record_source` and owner-routed HTTP mutations use this service. Snapshot writes are acknowledged before state changes; invalid or incomplete persisted data makes sources unavailable without preventing conversation loading. Source records remain outside prompt context, compaction summaries, and the active turn branch.

The bridge validates a session-bound client and attachment existence before forwarding mutations to the owning child. It checks the captured owner again after asynchronous attachment validation. Internal source errors use a private result envelope so the ACP transport does not log the original source payload. Public HTTP errors retain the design's status codes.

Daemon forks copy the current list after attachment copying, regenerate source IDs, and omit resources that cannot be mapped. Source-copy warnings are visible for both conversation forks and side tasks. The source copy waits for the temporary target writer to close before restoring the target session.

The TypeScript session client always uses REST for source operations, including when prompts use ACP. Web Shell provides one Sources section combining uploaded files and explicit references, Add/open controls, optional attachment metadata enrichment after acceptance, and metadata-only retry. It deduplicates by attachment ID, preserves historical files without backfilling metadata, and uses the existing guarded preview paths. The standalone Web Shell entry point enables the section; an embedding host's explicit section choices remain authoritative. HTML sources render as text, and link sources require explicit navigation.

## Acceptance and boundaries

Local evidence is kept in `.qwen/e2e-tests/session-sources.md` and its adjacent `session-sources-evidence` directory. It includes the global CLI baseline, package test logs, real daemon HTTP/SSE and recording evidence, browser interaction results, and screenshots. The model endpoint is a deterministic localhost fixture with a test-only credential; the CLI, daemon, source tool, storage, and Web Shell are real local builds. No external model endpoint is part of this acceptance run.

The acceptance covers registration, concurrency, capacity, client/owner rejection, persistence across restart, rewind, compaction, daemon fork and attachment copying, archive rejection, source notifications, preview behavior, and attachment retry without message resubmission. Final build, typecheck, focused tests, formatting, and lint results are recorded with the local evidence.

A recording writer that enters its existing degraded state after an I/O failure continues to reject writes. Restoring filesystem permissions alone does not replace that writer; resume/restart restores the last acknowledged source list before retrying. The feature does not bypass writer ownership or acknowledge memory-only changes.

This phase adds source APIs and complete source copying to daemon/Web Shell sessions. Standalone CLI, external ACP clients, and Python/Java SDKs receive no new source API. Cross-client source copying through standalone CLI `/branch` is outside this phase. Automatic attachment registration remains best-effort and its retry queue is held in browser memory, as specified in the design.

## Unified uploaded files and sources

The follow-up combines the previous Attachments and Sources sections into a single user-facing collection. Attachment bytes and explicit reference metadata retain their existing APIs. Registered attachment metadata supplies a preferred title; an unregistered upload stays visible as a plain file. Removing only its registration never recreates that source record, and does not imply deletion of the uploaded file. No source IDs or timestamps are fabricated for attachments.

The default entry point enables `sources` once. Legacy host `attachments` configuration still displays files; older daemons expose their file list without unsupported metadata actions. Add no longer contains a redundant existing-attachment picker. HTML from either registered or plain uploaded files opens as text. Independent failures preserve whichever side of the unified collection is available. Expanded acceptance is recorded under `.qwen/e2e-tests/unified-session-sources.md`.
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 @@ -362,6 +362,7 @@ describe('qwen serve — capabilities envelope', () => {
'session_events',
'session_artifacts',
'session_artifacts_persistence',
'session_sources',
Comment thread
callmeYe marked this conversation as resolved.
'slow_client_warning',
'typed_event_schema',
'session_set_model',
Expand Down
5 changes: 5 additions & 0 deletions packages/acp-bridge/src/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3193,6 +3193,11 @@ describe('createAcpSessionBridge', () => {
);
await bridge.releaseManagedConversationBinding(sessionId, expectation);
expect(artifactUpsertWorkspaceRoots).toEqual([]);
await bridge.getSessionSources(sessionId);
expect(handle.agent.extMethodCalls).toContainEqual({
method: 'qwen/session/sources/list',
params: { sessionId },
});
const deferredArtifactId = stableSessionArtifactId(
sessionId,
'url:https://example.com/deferred-artifact',
Expand Down
Loading
Loading