Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
50 changes: 50 additions & 0 deletions docs/design/daemon-extension-batch-toggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Daemon Extension Batch Activation

## Context

Extension Management V2 separates global default activation from exact
workspace overrides. Its singular global route writes `defaultActivation` by
stable Extension id and refreshes every runtime. Its singular workspace routes
write or clear an override for a selected trusted runtime and refresh only that
runtime.

Remote clients currently repeat those singular operations when toggling several
Extensions. The legacy `/workspace/extensions/*` compatibility surface has
different semantics: user scope writes a home-level path rule and workspace
scope is bound to the primary workspace. A batch route on that surface alone
therefore cannot optimize V2 clients.

## V2 contract

Add `extension_batch_activation_v2` as an independent capability so clients can
distinguish older `extension_management_v2` daemons. It exposes two queued
operations:

```text
PUT /extensions/activation
PUT /workspaces/:workspace/extensions/activation
```

Both accept 1–100 stable `extensionIds`, deduplicate them in request order, and
return one Extension operation id. The global body accepts `state` as `enabled`
or `disabled` and writes every valid target's `defaultActivation`. The workspace
body also accepts `inherit`; it clears each valid target's exact override using
the same legacy-rule masking semantics as the singular DELETE route.

Malformed ids or state reject the request before queueing. A well-formed but
missing id is a per-target `extension_not_found` error and does not block valid
targets. Successful global results report the resulting default activation.
Successful workspace results report the exact override (`null` for inherit) and
effective activation.

## Persistence and ownership

All valid targets are resolved from one loaded-Extension snapshot and written
under one Extension Store lock, producing one generation. The manager applies
that snapshot and refreshes its tool cache once.

The global batch is process-global: it refreshes all registered runtimes. The
workspace batch is selected-runtime scoped: it resolves the exact workspace id
or canonical path, requires that runtime to be trusted and open, writes only its
canonical workspace override, and refreshes only that runtime. It never falls
back to the primary runtime.
6 changes: 5 additions & 1 deletion docs/design/extension-management-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ The global surface is:

```text
GET /extensions
PUT /extensions/activation
POST /extensions/install
POST /extensions/check-updates
POST /extensions/:extensionId/update
Expand Down Expand Up @@ -113,6 +114,7 @@ The workspace projection is:

```text
GET /workspaces/:workspace/extensions
PUT /workspaces/:workspace/extensions/activation
PUT /workspaces/:workspace/extensions/:extensionId/activation
DELETE /workspaces/:workspace/extensions/:extensionId/activation
POST /workspaces/:workspace/extensions/refresh
Expand Down Expand Up @@ -211,7 +213,9 @@ runtime. The legacy operation endpoint maps V2 warning completion back to the
published legacy refresh-error status.

Clients must check `extension_management_v2`; neither daemon mode nor another
workspace capability implies this API. The abandoned
workspace capability implies this API. Batch activation additionally requires
`extension_batch_activation_v2`, because older V2 daemons expose only singular
activation routes. The abandoned
`workspace_qualified_extensions` proposal is not part of the protocol.

## Non-goals
Expand Down
2 changes: 2 additions & 0 deletions docs/developers/daemon/11-capabilities-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ Workspace read-only snapshots: `workspace_mcp`, `workspace_skills`, `workspace_p

Extension management: `extension_management_v2` adds the global `/extensions/*` catalog/mutation/operation contract and the workspace activation projection. It is separate from the published `workspace_extensions` compatibility surface and from `workspace_qualified_rest_core`.

V2 Extension batch activation: `extension_batch_activation_v2` adds queued global default-activation and selected-workspace override batches to `extension_management_v2`. Clients must pre-flight it independently because older V2 daemons expose only singular activation routes.

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).
Expand Down
20 changes: 20 additions & 0 deletions docs/developers/daemon/13-sdk-daemon-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ await client

`DaemonSkillBatchToggleResult` contains ordered successful `results`, per-target `errors`, and batch-level activation/session-refresh counts. The daemon persists valid targets together and refreshes active sessions once; one expected target error does not block other valid targets. The method throws only on a non-200 response; a 200 does not mean every target was applied, so always inspect `errors` before treating the batch as successful.

V2 Extension batch activation retains the asynchronous Extension operation model. Pre-flight `extension_batch_activation_v2`, submit a global default batch or a selected-workspace override batch, then poll it with the existing operation helper:

```ts
const globalHandle = await client.setExtensionDefaultActivations(
[formatterId, reviewToolsId],
'disabled',
'dashboard-1',
);
const workspaceHandle = await client
.workspaceByCwd('/work/secondary')
.setExtensionActivations(
[formatterId, reviewToolsId],
'inherit',
'dashboard-1',
);
const operation = await client.waitForExtensionOperation(workspaceHandle);
```

The terminal operation result contains ordered `results` and per-target `extension_not_found` `errors`. Valid targets share one Extension Store generation and one reconciliation pass. Global default batches reconcile every registered runtime; workspace batches resolve and reconcile only the selected trusted runtime. Workspace `inherit` clears the exact override and reports the resulting effective activation.

Workspace display names are optional presentation metadata. Pre-flight `capabilities.features.includes('workspace_display_name')`; workspace ids and canonical paths remain the only selectors, and duplicate display names are valid.

```ts
Expand Down
22 changes: 19 additions & 3 deletions docs/developers/qwen-serve-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ registry. Clients **must** gate UI off `features`, not off `mode` (per design
'workspace_file_read', 'workspace_file_bytes', 'workspace_file_write',
'session_approval_mode_control', 'workspace_tool_toggle', 'workspace_skill_toggle',
'workspace_skill_batch_toggle',
'extension_batch_activation_v2',
'workspace_settings', 'workspace_init', 'workspace_mcp_restart',
'session_recap', 'session_generation', 'session_btw', 'session_shell_command',
'mcp_workspace_pool', 'mcp_pool_restart',
Expand Down Expand Up @@ -244,7 +245,7 @@ registry. Clients **must** gate UI off `features`, not off `mode` (per design

`session_info` advertises `GET /workspace/:id/session-info` and its `/workspaces/:workspace/session-info` twin. The response aggregates persisted active and archived session counts without hydrating list metadata. It is an explicit O(n) disk scan and must not be polled; clients should treat `truncated: true` as a lower-bound result.

`session_approval_mode_control`, `workspace_tool_toggle`, `workspace_skill_toggle`, `workspace_skill_batch_toggle`, `workspace_init`, and `workspace_mcp_restart` advertise the mutation control routes documented below. They are strict-gated by the mutation gate (a daemon configured without a bearer token rejects them with 401 `token_required`). Older daemons return `404`; pre-flight each tag before exposing the corresponding affordance.
`session_approval_mode_control`, `workspace_tool_toggle`, `workspace_skill_toggle`, `workspace_skill_batch_toggle`, `extension_batch_activation_v2`, `workspace_init`, and `workspace_mcp_restart` advertise the mutation control routes documented below. They are strict-gated by the mutation gate (a daemon configured without a bearer token rejects them with 401 `token_required`). Older daemons return `404`; pre-flight each tag before exposing the corresponding affordance.

`mcp_guardrails` (issue [#4175](https://github.com/QwenLM/qwen-code/issues/4175) PR 14) covers the MCP budget surface: the `clientCount` / `clientBudget` / `budgetMode` / `budgets[]` fields on `GET /workspace/mcp`, the `disabledReason` field on per-server cells, and the `--mcp-client-budget` / `--mcp-budget-mode` CLI flags. Older daemons omit the new fields entirely; SDK clients pre-flight this tag before relying on `budgets[]` semantics. The registry descriptor also carries `modes: ['warn', 'enforce']` for future feature-modes exposure — for now, clients infer mode from the snapshot's `budgetMode` field. Server refusal under `enforce` mode is deterministic by `Object.entries(mcpServers)` declaration order; a future scope-precedence layer (if qwen-code adopts one) would shift this to "lowest-precedence first" to mirror claude-code's `plugin < user < project < local` convention.

Expand All @@ -265,20 +266,24 @@ The same tag also exposes workspace-qualified project-agent CRUD at `/workspaces

`extension_management_v2` advertises a user-level extension catalog and mutation surface at `/extensions/*`, plus workspace activation projections at `/workspaces/:workspace/extensions/*`. Artifacts are global; workspace routes expose only projection reads, exact activation overrides, and runtime refresh. Reads may target an untrusted registered workspace, while activation, refresh, and workspace-scoped install require a trusted target. Slow mutations use daemon-local operations at `/extensions/operations/:operationId`; store generation, not operation history, is authoritative across restart and across daemons. The published `workspace_extensions` capability and `/workspace/extensions/*` routes remain a primary-workspace compatibility adapter. Clients must preflight `extension_management_v2` and must not infer it from daemon mode or `workspace_qualified_rest_core`.

`extension_batch_activation_v2` adds `PUT /extensions/activation` and `PUT /workspaces/:workspace/extensions/activation`. Both accept 1–100 stable `extensionIds`, preserve first-seen order, report well-formed missing ids as per-target errors, persist valid targets in one generation, and return one `202` operation handle. The global route accepts `state: "enabled" | "disabled"`, writes V2 `defaultActivation`, and reconciles every registered runtime. The workspace route also accepts `"inherit"`, applies or clears exact overrides for the selected trusted runtime, and reconciles only that runtime.

### Extension Management V2 wire contract

All routes use the daemon bearer authentication rules above. `X-Qwen-Client-Id` is optional for the V2 mutation routes; when supplied, it must identify a client registered with one of the mutation's target workspace runtimes. `:extensionId` is the lowercase 64-hex extension identity. `:workspace` resolves as an exact workspace id first and otherwise as a URL-encoded absolute cwd after canonicalization.

| Method and path | Success |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `GET /extensions` | `200` global artifact catalog |
| `PUT /extensions/activation` | `202` global default-activation batch operation |
| `PUT /extensions/:extensionId/activation` | `202` global default-activation operation |
| `POST /extensions/install` | `202` install operation |
| `POST /extensions/check-updates` | `202` update-check operation |
| `POST /extensions/:extensionId/update` | `202` update operation |
| `DELETE /extensions/:extensionId` | `202` uninstall operation, or idempotent `204` when the extension is absent |
| `GET /extensions/operations/:operationId` | `200` operation snapshot |
| `GET /workspaces/:workspace/extensions` | `200` workspace activation projection |
| `PUT /workspaces/:workspace/extensions/activation` | `202` exact workspace-activation batch operation |
| `PUT /workspaces/:workspace/extensions/:extensionId/activation` | `202` exact workspace-activation operation |
| `DELETE /workspaces/:workspace/extensions/:extensionId/activation` | `202` clear-override operation |
| `POST /workspaces/:workspace/extensions/refresh` | `202` runtime-refresh operation |
Expand Down Expand Up @@ -354,6 +359,17 @@ Global and workspace activation `PUT` requests use the same body:

`state` is `enabled` or `disabled`. Update, uninstall, check-updates, clear-activation, and refresh requests have no required body.

Batch activation requests add `extensionIds`:

```json
{
Comment thread
callmeYe marked this conversation as resolved.
Resolved
"extensionIds": ["<extension-id-1>", "<extension-id-2>"],
"state": "disabled"
}
```

The workspace batch also accepts `"state": "inherit"`. Terminal global results contain `extensionId`, `name`, and `defaultActivation`; workspace results contain `extensionId`, `name`, `workspaceActivation` (`null` for inherit), and `effectiveActivation`. Missing valid ids appear in `errors` with code `extension_not_found`.

Every accepted asynchronous mutation returns:

```http
Expand Down Expand Up @@ -383,7 +399,7 @@ An operation snapshot has this shape:
}
```

`status` transitions from `queued` to `running`, then to `succeeded`, `succeeded_with_warnings`, or `failed`. While running, `phase` is `preparing`, `committing`, or `reconciling`. Terminal success may include `result` with `status` equal to `installed`, `enabled`, `disabled`, `updated`, `uninstalled`, `checked`, or `refreshed`; reconciliation results can additionally contain `refreshed`, `failed`, and `error`. Update checks return `result.states`, keyed by extension name, with values such as `checking for updates`, `update available`, `up to date`, `not updatable`, or `error`.
`status` transitions from `queued` to `running`, then to `succeeded`, `succeeded_with_warnings`, or `failed`. While running, `phase` is `preparing`, `committing`, or `reconciling`. Terminal success may include `result` with `status` equal to `installed`, `enabled`, `disabled`, `updated`, `uninstalled`, `checked`, or `refreshed`; reconciliation results can additionally contain `refreshed`, `failed`, and `error`, while batch activation results contain ordered `results` and `errors`. Update checks return `result.states`, keyed by extension name, with values such as `checking for updates`, `update available`, `up to date`, `not updatable`, or `error`.

A durable commit followed by incomplete cleanup or runtime reconciliation is not reported as a failed mutation. It returns `succeeded_with_warnings` and preserves the committed result:

Expand Down Expand Up @@ -414,7 +430,7 @@ A durable commit followed by incomplete cleanup or runtime reconciliation is not

Warning `workspaceId` and `code` are optional; `workspaceCwd` and `error` are always present. Clients should display warnings, refresh their catalog/projection, and must not retry the durable mutation blindly.

Validation and authorization failures are synchronous HTTP errors using `{ "error": "...", "code": "..." }` when a stable code exists. Important cases are `400 invalid_extension_id`, `400 invalid_extension_activation`, `400 workspace_mismatch`, `403 untrusted_workspace`, `404 extension_operation_not_found`, and `429 extension_queue_full`. Install validation also returns `400` for invalid source/ref/registry options, missing consent, or missing/invalid initial activation. A mutation that fails after `202` is represented, while retained in operation history, with `status: "failed"`, `error`, and an optional stable `code`; common codes include `extension_prepare_timeout` and `extension_conflict`. HTTP `404` for an operation does not imply rollback because operation history is not durable.
Validation and authorization failures are synchronous HTTP errors using `{ "error": "...", "code": "..." }` when a stable code exists. Important cases are `400 invalid_extension_id`, `400 invalid_extension_ids`, `400 invalid_extension_activation`, `400 workspace_mismatch`, `403 untrusted_workspace`, `404 extension_operation_not_found`, and `429 extension_queue_full`. Install validation also returns `400` for invalid source/ref/registry options, missing consent, or missing/invalid initial activation. A mutation that fails after `202` is represented, while retained in operation history, with `status: "failed"`, `error`, and an optional stable `code`; common codes include `extension_prepare_timeout` and `extension_conflict`. HTTP `404` for an operation does not imply rollback because operation history is not durable.
Comment thread
callmeYe marked this conversation as resolved.
Resolved

`daemon_status` advertises `GET /daemon/status`, the consolidated read-only
operator diagnostic snapshot documented below.
Expand Down
Loading
Loading