refactor(cli): Finish serve kebab-case filenames (#5604)

This commit is contained in:
jinye 2026-06-22 21:15:40 +08:00 committed by GitHub
parent f29f210201
commit a8863c203c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 88 additions and 111 deletions

View file

@ -25,7 +25,7 @@ standard ACP method (model switch, workspace introspection, heartbeat,
multi-client permission policy, SSE backpressure tuning). Rationale in §5. multi-client permission policy, SSE backpressure tuning). Rationale in §5.
A complete, locally-runnable reference implementation ships in this PR A complete, locally-runnable reference implementation ships in this PR
(`packages/cli/src/serve/acpHttp/`) plus a verification harness (`packages/cli/src/serve/acp-http/`) plus a verification harness
(`scripts/acp-http-smoke.mjs`). (`scripts/acp-http-smoke.mjs`).
--- ---
@ -151,15 +151,15 @@ client ────────────────────────
qwen --acp child qwen --acp child
``` ```
### 3.1 New module layout (`packages/cli/src/serve/acpHttp/`) ### 3.1 New module layout (`packages/cli/src/serve/acp-http/`)
| File | Responsibility | | File | Responsibility |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index.ts` | `mountAcpHttp(app, bridge, opts)` — registers `/acp` routes on the existing Express app. | | `index.ts` | `mountAcpHttp(app, bridge, opts)` — registers `/acp` routes on the existing Express app. |
| `connectionRegistry.ts` | `Acp-Connection-Id``AcpConnection` (connection SSE writer, `Map<sessionId, SessionStream>`, pending agent→client requests by JSON-RPC id, monotonic id allocator). TTL + DELETE cleanup. | | `connection-registry.ts` | `Acp-Connection-Id``AcpConnection` (connection SSE writer, `Map<sessionId, SessionStream>`, pending agent→client requests by JSON-RPC id, monotonic id allocator). TTL + DELETE cleanup. |
| `jsonRpc.ts` | JSON-RPC 2.0 parse/validate/serialize helpers; error codes (`-32600` etc.); `_qwen/` namespace guard. | | `json-rpc.ts` | JSON-RPC 2.0 parse/validate/serialize helpers; error codes (`-32600` etc.); `_qwen/` namespace guard. |
| `dispatch.ts` | Maps inbound JSON-RPC methods → `HttpAcpBridge` calls. Maps `BridgeEvent`s → outbound JSON-RPC frames. The translation table (§4). | | `dispatch.ts` | Maps inbound JSON-RPC methods → `HttpAcpBridge` calls. Maps `BridgeEvent`s → outbound JSON-RPC frames. The translation table (§4). |
| `sseStream.ts` | Long-lived SSE writer (reuses the backpressure/heartbeat pattern from `server.ts`). Distinct from REST `/events` (different framing: full JSON-RPC objects, not qwen event envelopes). | | `sse-stream.ts` | Long-lived SSE writer (reuses the backpressure/heartbeat pattern from `server.ts`). Distinct from REST `/events` (different framing: full JSON-RPC objects, not qwen event envelopes). |
No change to `bridge.ts` / `eventBus.ts` (additive consumer only). No change to `bridge.ts` / `eventBus.ts` (additive consumer only).
@ -284,7 +284,7 @@ thin compat shim over `/acp` (separate, later PR).
- `session/request_permission` agent→client round-trip. - `session/request_permission` agent→client round-trip.
- `_qwen/session/set_model` extension as the worked example of #2. - `_qwen/session/set_model` extension as the worked example of #2.
- Bearer-auth + host allowlist reuse (same middleware as REST). - Bearer-auth + host allowlist reuse (same middleware as REST).
- Unit tests (`acpHttp/*.test.ts`) + a black-box smoke script driving a real daemon. - Unit tests (`acp-http/*.test.ts`) + a black-box smoke script driving a real daemon.
**Deferred (documented, not built now):** **Deferred (documented, not built now):**
@ -309,7 +309,7 @@ thin compat shim over `/acp` (separate, later PR).
- Trigger a tool needing permission → assert `session/request_permission` request, - Trigger a tool needing permission → assert `session/request_permission` request,
POST a grant response → assert prompt completes. POST a grant response → assert prompt completes.
- `POST {_qwen/session/set_model}` → assert model switch + `session/update`. - `POST {_qwen/session/set_model}` → assert model switch + `session/update`.
4. Vitest: `acpHttp/*.test.ts` green. 4. Vitest: `acp-http/*.test.ts` green.
--- ---
@ -326,11 +326,11 @@ thin compat shim over `/acp` (separate, later PR).
## 10. Implementation & verification log (v1) ## 10. Implementation & verification log (v1)
Implemented in `packages/cli/src/serve/acpHttp/` (`jsonRpc.ts`, `sseStream.ts`, Implemented in `packages/cli/src/serve/acp-http/` (`json-rpc.ts`, `sse-stream.ts`,
`connectionRegistry.ts`, `dispatch.ts`, `index.ts`), mounted from `server.ts` `connection-registry.ts`, `dispatch.ts`, `index.ts`), mounted from `server.ts`
via `mountAcpHttp(app, bridge, { boundWorkspace })`. via `mountAcpHttp(app, bridge, { boundWorkspace })`.
### Automated (`packages/cli/src/serve/acpHttp/*.test.ts`) ### Automated (`packages/cli/src/serve/acp-http/*.test.ts`)
`transport.test.ts` boots a real Express server + the real `mountAcpHttp` over `transport.test.ts` boots a real Express server + the real `mountAcpHttp` over
a controllable fake bridge and drives it with `fetch` + manual SSE parsing. a controllable fake bridge and drives it with `fetch` + manual SSE parsing.
@ -496,8 +496,8 @@ Another reviewer pass (qwen3.7-max). Suite **30 tests**, live re-verified.
| F3 | **P2** | 503 connection-cap rejection had no stderr log. | `writeStderrLine` with the cap value. | | F3 | **P2** | 503 connection-cap rejection had no stderr log. | `writeStderrLine` with the cap value. |
| F4 | **P2** | `_qwen/notify stream_error` spread let `event.data.kind` shadow the discriminator. | Spread first, then `kind: 'stream_error'`. | | F4 | **P2** | `_qwen/notify stream_error` spread let `event.data.kind` shadow the discriminator. | Spread first, then `kind: 'stream_error'`. |
| F5 | **P2** | `MAX_WORKSPACE_PATH_LENGTH` redeclared (`= 4096`) vs the canonical `fs/paths.js`. | Import from `../fs/paths.js` (no divergence). | | F5 | **P2** | `MAX_WORKSPACE_PATH_LENGTH` redeclared (`= 4096`) vs the canonical `fs/paths.js`. | Import from `../fs/paths.js` (no divergence). |
| F6 | **P2** | `isObjectParams` duplicated `jsonRpc.isObject`. | Import `isObject`. | | F6 | **P2** | `isObjectParams` duplicated `json-rpc.isObject`. | Import `isObject`. |
| F7 | **P2** | Raw `process.stderr.write` in `index.ts`/`sseStream.ts` vs `writeStderrLine` elsewhere. | Unified on `writeStderrLine` across the module. | | F7 | **P2** | Raw `process.stderr.write` in `index.ts`/`sse-stream.ts` vs `writeStderrLine` elsewhere. | Unified on `writeStderrLine` across the module. |
--- ---

View file

@ -37,19 +37,19 @@
**Subsystems**: **Subsystems**:
| Path | Role | | Path | Role |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `serve/fs/` | `WorkspaceFileSystem` factory plus `policy.ts` (size/trust/binary checks), `paths.ts` (canonicalize, resolveWithin, symlink rejection), `audit.ts`, and typed `FsError` values. | | `serve/fs/` | `WorkspaceFileSystem` factory plus `policy.ts` (size/trust/binary checks), `paths.ts` (canonicalize, resolveWithin, symlink rejection), `audit.ts`, and typed `FsError` values. |
| `serve/routes/workspace-file-read.ts`, `workspace-file-write.ts` | HTTP handlers for `GET /file`, `GET /file/bytes`, `POST /file/write`, and `POST /file/edit`. | | `serve/routes/workspace-file-read.ts`, `workspace-file-write.ts` | HTTP handlers for `GET /file`, `GET /file/bytes`, `POST /file/write`, and `POST /file/edit`. |
| `serve/workspace-memory.ts` | `GET/POST /workspace/memory` (QWEN.md CRUD). | | `serve/workspace-memory.ts` | `GET/POST /workspace/memory` (QWEN.md CRUD). |
| `serve/workspace-agents.ts` | `GET/POST/DELETE /workspace/agents` (subagent CRUD). | | `serve/workspace-agents.ts` | `GET/POST/DELETE /workspace/agents` (subagent CRUD). |
| `serve/daemon-status-provider.ts` | Env snapshot plus daemon-host preflight cells: Node version, CLI entry, workspace stat, ripgrep, git, npm. | | `serve/daemon-status-provider.ts` | Env snapshot plus daemon-host preflight cells: Node version, CLI entry, workspace stat, ripgrep, git, npm. |
| `serve/permission-audit.ts` | `PermissionAuditRing` (512-entry FIFO) and `createPermissionAuditPublisher`. | | `serve/permission-audit.ts` | `PermissionAuditRing` (512-entry FIFO) and `createPermissionAuditPublisher`. |
| `serve/auth/device-flow.ts`, `qwen-device-flow-provider.ts` | Device-flow OAuth routes. See [`12-auth-security.md`](./12-auth-security.md). | | `serve/auth/device-flow.ts`, `qwen-device-flow-provider.ts` | Device-flow OAuth routes. See [`12-auth-security.md`](./12-auth-security.md). |
| `serve/daemonLogger.ts` | `DaemonLogger` structured file logs. See [`19-observability.md`](./19-observability.md). | | `serve/daemon-logger.ts` | `DaemonLogger` structured file logs. See [`19-observability.md`](./19-observability.md). |
| `serve/debug-mode.ts` | Shared `isServeDebugMode()` predicate controlling verbose error context in HTTP responses. | | `serve/debug-mode.ts` | Shared `isServeDebugMode()` predicate controlling verbose error context in HTTP responses. |
| `serve/acpHttp/` | ACP Streamable HTTP transport (RFD #721), mounted at `/acp`. Seven files implement JSON-RPC POST, SSE GET, DELETE teardown, and shared bridge usage in parallel with the REST surface. | | `serve/acp-http/` | ACP Streamable HTTP transport (RFD #721), mounted at `/acp`. Seven files implement JSON-RPC POST, SSE GET, DELETE teardown, and shared bridge usage in parallel with the REST surface. |
| `serve/demo.ts` | Self-contained inline HTML for `GET /demo`: browser debug console with chat UI, event log, and workspace inspector. On loopback without `--require-auth`, it is registered **before** `bearerAuth`; on non-loopback or with `--require-auth`, it is registered **after** `bearerAuth`. Served with CSP `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'` plus `X-Frame-Options: DENY`. | | `serve/demo.ts` | Self-contained inline HTML for `GET /demo`: browser debug console with chat UI, event log, and workspace inspector. On loopback without `--require-auth`, it is registered **before** `bearerAuth`; on non-loopback or with `--require-auth`, it is registered **after** `bearerAuth`. Served with CSP `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'` plus `X-Frame-Options: DENY`. |
**Re-export shims** for compatibility with pre-F1 import paths: **Re-export shims** for compatibility with pre-F1 import paths:

View file

@ -10,7 +10,7 @@
| ------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `QWEN_SERVE_DEBUG` stderr logs | `bridge.ts` and call sites | Env values `1` / `true` / `on` / `yes` (case-insensitive) print `qwen serve debug: ...` lines to stderr. | | `QWEN_SERVE_DEBUG` stderr logs | `bridge.ts` and call sites | Env values `1` / `true` / `on` / `yes` (case-insensitive) print `qwen serve debug: ...` lines to stderr. |
| OpenTelemetry span instrumentation | `server.ts` `daemonTelemetryMiddleware` | Each HTTP request is wrapped in `withDaemonRequestSpan`; attributes include route, sessionId, clientId, and status code. Permission routes have dedicated spans. Prompt lifecycle is traced end-to-end. Configuration lives in `settings.json` `telemetry`. | | OpenTelemetry span instrumentation | `server.ts` `daemonTelemetryMiddleware` | Each HTTP request is wrapped in `withDaemonRequestSpan`; attributes include route, sessionId, clientId, and status code. Permission routes have dedicated spans. Prompt lifecycle is traced end-to-end. Configuration lives in `settings.json` `telemetry`. |
| `DaemonLogger` structured file logs | `serve/daemonLogger.ts` | Structured JSON-like log lines are written to a file. Boot prints `daemon log -> <path>`. Supports `info` / `warn` / `error` levels, with structured fields such as `route`, `sessionId`, `clientId`, `childPid`, and `channelId`. | | `DaemonLogger` structured file logs | `serve/daemon-logger.ts` | Structured JSON-like log lines are written to a file. Boot prints `daemon log -> <path>`. Supports `info` / `warn` / `error` levels, with structured fields such as `route`, `sessionId`, `clientId`, `childPid`, and `channelId`. |
| Per-request access-log middleware | `server.ts`, registered before `bearerAuth` | Logs `method`, `path`, `status`, `durationMs`, `sessionId`, and `clientId` after each request. Skips `GET /health` and heartbeat. 4xx+ uses `warn`; success uses `info`. | | Per-request access-log middleware | `server.ts`, registered before `bearerAuth` | Logs `method`, `path`, `status`, `durationMs`, `sessionId`, and `clientId` after each request. Skips `GET /health` and heartbeat. 4xx+ uses `warn`; success uses `info`. |
| `/health` | `server.ts` route | Liveness probe; `?deep=1` returns extended details. | | `/health` | `server.ts` route | Liveness probe; `?deep=1` returns extended details. |
| `/capabilities` | `server.ts` route | Preflight feature discovery. See [`11-capabilities-versioning.md`](./11-capabilities-versioning.md). | | `/capabilities` | `server.ts` route | Preflight feature discovery. See [`11-capabilities-versioning.md`](./11-capabilities-versioning.md). |
@ -20,7 +20,7 @@
| `/workspace/skills`, `/workspace/providers` | Routes | ACP-side live snapshots; return empty idle data when no session exists. | | `/workspace/skills`, `/workspace/providers` | Routes | ACP-side live snapshots; return empty idle data when no session exists. |
| Per-session SSE | `GET /session/:id/events` | Real-time event stream. | | Per-session SSE | `GET /session/:id/events` | Real-time event stream. |
| `/demo` debug console | `GET /demo` (`packages/cli/src/serve/demo.ts`) | Browser-accessible single-page console: chat, event log, workspace inspector, and permission UX. On loopback, `http://127.0.0.1:4170/demo` is the quickest end-to-end validation path without writing SDK code. Registration rules are in [`02-serve-runtime.md`](./02-serve-runtime.md). | | `/demo` debug console | `GET /demo` (`packages/cli/src/serve/demo.ts`) | Browser-accessible single-page console: chat, event log, workspace inspector, and permission UX. On loopback, `http://127.0.0.1:4170/demo` is the quickest end-to-end validation path without writing SDK code. Registration rules are in [`02-serve-runtime.md`](./02-serve-runtime.md). |
| `PermissionAuditRing` | `permission-audit.ts` | In-memory FIFO of 512 permission decisions. | | `PermissionAuditRing` | `permission-audit.ts` | In-memory FIFO of 512 permission decisions. |
| Mediator `decisionReason` audit | `permissionMediator.ts` | Internal structured record explaining why a permission request resolved the way it did. | | Mediator `decisionReason` audit | `permissionMediator.ts` | Internal structured record explaining why a permission request resolved the way it did. |
## What does not exist today ## What does not exist today
@ -143,7 +143,7 @@ flowchart TD
## References ## References
- `packages/cli/src/serve/daemon-status-provider.ts` - `packages/cli/src/serve/daemon-status-provider.ts`
- `packages/cli/src/serve/daemonLogger.ts` (`DaemonLogger`, `buildDaemonLogLine`) - `packages/cli/src/serve/daemon-logger.ts` (`DaemonLogger`, `buildDaemonLogLine`)
- `packages/cli/src/serve/debug-mode.ts` (`isServeDebugMode`) - `packages/cli/src/serve/debug-mode.ts` (`isServeDebugMode`)
- `packages/acp-bridge/src/permissionMediator.ts` (`PermissionDecisionReason`) - `packages/acp-bridge/src/permissionMediator.ts` (`PermissionDecisionReason`)
- `packages/cli/src/serve/server.ts` (`daemonTelemetryMiddleware`, access-log middleware) - `packages/cli/src/serve/server.ts` (`daemonTelemetryMiddleware`, access-log middleware)

View file

@ -82,17 +82,13 @@ export const legacyFilenames = [
'cronScheduler', 'cronScheduler',
'customBanner', 'customBanner',
'customMatchers', 'customMatchers',
'daemonStatusProvider',
'DaemonTuiAdapter',
'dangerousRules', 'dangerousRules',
'DataProcessor', 'DataProcessor',
'debugLogger', 'debugLogger',
'debugMode',
'deepMerge', 'deepMerge',
'deleteCommand', 'deleteCommand',
'denialTracking', 'denialTracking',
'detectTodoChanges', 'detectTodoChanges',
'deviceFlow',
'dialogScopeUtils', 'dialogScopeUtils',
'diffCommand', 'diffCommand',
'diffOptions', 'diffOptions',
@ -109,7 +105,6 @@ export const legacyFilenames = [
'editorSettingsManager', 'editorSettingsManager',
'envInterpolator', 'envInterpolator',
'environmentContext', 'environmentContext',
'envSnapshot',
'envVarResolver', 'envVarResolver',
'errorCodes', 'errorCodes',
'errorHandler', 'errorHandler',
@ -205,7 +200,6 @@ export const legacyFilenames = [
'LlmRewriter', 'LlmRewriter',
'loadedSettingsAdapter', 'loadedSettingsAdapter',
'loggingContentGenerator', 'loggingContentGenerator',
'loopbackBinds',
'loopDetectionService', 'loopDetectionService',
'lowlightLoader', 'lowlightLoader',
'LruCache', 'LruCache',
@ -283,7 +277,6 @@ export const legacyFilenames = [
'quitCommand', 'quitCommand',
'quotaErrorDetection', 'quotaErrorDetection',
'qwenContentGenerator', 'qwenContentGenerator',
'qwenDeviceFlowProvider',
'qwenIgnoreParser', 'qwenIgnoreParser',
'qwenOAuth2', 'qwenOAuth2',
'rateLimit', 'rateLimit',
@ -309,7 +302,6 @@ export const legacyFilenames = [
'ripgrepUtils', 'ripgrepUtils',
'rulesDiscovery', 'rulesDiscovery',
'runBudget', 'runBudget',
'runQwenServe',
'runtimeDiagnostics', 'runtimeDiagnostics',
'runtimeFetchOptions', 'runtimeFetchOptions',
'runtimeOutputDirContext', 'runtimeOutputDirContext',
@ -511,12 +503,7 @@ export const legacyFilenames = [
'warningHandler', 'warningHandler',
'windowsPath', 'windowsPath',
'windowTitle', 'windowTitle',
'workspaceAgents',
'workspaceContext', 'workspaceContext',
'workspaceFileRead',
'workspaceFileSystem',
'workspaceFileWrite',
'workspaceMemory',
'worktreeCleanup', 'worktreeCleanup',
'worktreeSessionService', 'worktreeSessionService',
'worktreeStartup', 'worktreeStartup',
@ -525,10 +512,7 @@ export const legacyFilenames = [
'CodeColorizer', 'CodeColorizer',
'SKILL', 'SKILL',
'TeamManager', 'TeamManager',
'a2uiAction',
'acpSessionBridge',
'asciiCharts', 'asciiCharts',
'bridgeFileSystemAdapter',
'btwUtils', 'btwUtils',
'cdCommand', 'cdCommand',
'claudeMcpImport', 'claudeMcpImport',
@ -537,12 +521,9 @@ export const legacyFilenames = [
'compressFastCommand', 'compressFastCommand',
'concurrencyLimiter', 'concurrencyLimiter',
'configHash', 'configHash',
'connectionRegistry',
'corruptFile', 'corruptFile',
'cronTasksFile', 'cronTasksFile',
'cronTasksLock', 'cronTasksLock',
'daemonLogger',
'daemonStatus',
'enterPlanMode', 'enterPlanMode',
'extensionPreferences', 'extensionPreferences',
'forkCommand', 'forkCommand',
@ -555,7 +536,6 @@ export const legacyFilenames = [
'importConfigCommand', 'importConfigCommand',
'inlineMediaLimit', 'inlineMediaLimit',
'instructionsLoadedCallback', 'instructionsLoadedCallback',
'jsonRpc',
'leaderPermissionBridge', 'leaderPermissionBridge',
'liveAgentPanelVisibility', 'liveAgentPanelVisibility',
'mcpApprovals', 'mcpApprovals',
@ -564,7 +544,6 @@ export const legacyFilenames = [
'mcpServers', 'mcpServers',
'midTurnUserMessage', 'midTurnUserMessage',
'normalizeDisabledTools', 'normalizeDisabledTools',
'permissionAudit',
'planApprovalGate', 'planApprovalGate',
'promptAddendum', 'promptAddendum',
'retryErrorClassification', 'retryErrorClassification',
@ -578,7 +557,6 @@ export const legacyFilenames = [
'settingsWatcher', 'settingsWatcher',
'sleepInhibitor', 'sleepInhibitor',
'sourceRegistry', 'sourceRegistry',
'sseStream',
'statsDataService', 'statsDataService',
'tasksSnapshot', 'tasksSnapshot',
'teamHelpers', 'teamHelpers',
@ -587,15 +565,11 @@ export const legacyFilenames = [
'toolCallIdUtils', 'toolCallIdUtils',
'toolResultCleanup', 'toolResultCleanup',
'toolResultDisplayCompaction', 'toolResultDisplayCompaction',
'transportStream',
'usageHistoryService', 'usageHistoryService',
'useMcpApproval', 'useMcpApproval',
'useResizeSettleRepaint', 'useResizeSettleRepaint',
'useStatsDialog', 'useStatsDialog',
'useTeamInProcess', 'useTeamInProcess',
'userMemory', 'userMemory',
'webShellStatic',
'workflowsCommand', 'workflowsCommand',
'workspaceSettings',
'wsStream',
]; ];

View file

@ -5,8 +5,8 @@
*/ */
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { ConnectionRegistry } from './connectionRegistry.js'; import { ConnectionRegistry } from './connection-registry.js';
import type { TransportStream } from './transportStream.js'; import type { TransportStream } from './transport-stream.js';
class FakeStream implements TransportStream { class FakeStream implements TransportStream {
isClosed = false; isClosed = false;

View file

@ -6,8 +6,8 @@
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import { writeStderrLine } from '../../utils/stdioHelpers.js'; import { writeStderrLine } from '../../utils/stdioHelpers.js';
import { logSafe } from './jsonRpc.js'; import { logSafe } from './json-rpc.js';
import type { TransportStream } from './transportStream.js'; import type { TransportStream } from './transport-stream.js';
/** /**
* Per-stream cap on frames buffered before the client attaches its SSE * Per-stream cap on frames buffered before the client attaches its SSE

View file

@ -50,7 +50,7 @@ import type {
DaemonWorkspaceService, DaemonWorkspaceService,
WorkspaceRequestContext, WorkspaceRequestContext,
} from '../workspace-service/types.js'; } from '../workspace-service/types.js';
import type { AcpConnection } from './connectionRegistry.js'; import type { AcpConnection } from './connection-registry.js';
import { import {
QWEN_META_KEY, QWEN_META_KEY,
QWEN_METHOD_NS, QWEN_METHOD_NS,
@ -68,7 +68,7 @@ import {
type JsonRpcInbound, type JsonRpcInbound,
type JsonRpcRequest, type JsonRpcRequest,
type JsonRpcResponse, type JsonRpcResponse,
} from './jsonRpc.js'; } from './json-rpc.js';
function errMsg(err: unknown): string { function errMsg(err: unknown): string {
return err instanceof Error ? err.message : String(err); return err instanceof Error ? err.message : String(err);
@ -2697,5 +2697,5 @@ export class AcpDispatcher {
} }
} }
// Re-export so tests can reference the request type without the jsonRpc path. // Re-export so tests can reference the request type without the json-rpc path.
export type { JsonRpcRequest }; export type { JsonRpcRequest };

View file

@ -18,11 +18,11 @@ import { AcpDispatcher } from './dispatch.js';
import { import {
ConnectionRegistry, ConnectionRegistry,
type AcpConnection, type AcpConnection,
} from './connectionRegistry.js'; } from './connection-registry.js';
import { SseStream } from './sseStream.js'; import { SseStream } from './sse-stream.js';
import { WsStream } from './wsStream.js'; import { WsStream } from './ws-stream.js';
import type { RateLimitTier } from '../rate-limit.js'; import type { RateLimitTier } from '../rate-limit.js';
import { RPC, error as rpcError, isRequest, parseInbound } from './jsonRpc.js'; import { RPC, error as rpcError, isRequest, parseInbound } from './json-rpc.js';
export const ACP_CONNECTION_HEADER = 'acp-connection-id'; export const ACP_CONNECTION_HEADER = 'acp-connection-id';
export const ACP_SESSION_HEADER = 'acp-session-id'; export const ACP_SESSION_HEADER = 'acp-session-id';
@ -362,7 +362,7 @@ export function mountAcpHttp(
// stream. A reconnect already installed a newer stream — the prompt // stream. A reconnect already installed a newer stream — the prompt
// must survive the old stream's close. CONTRACT: this identity guard // must survive the old stream's close. CONTRACT: this identity guard
// pairs with `attachSessionStream`'s install-before-close ordering // pairs with `attachSessionStream`'s install-before-close ordering
// (connectionRegistry.ts) — keep both in lockstep. // (connection-registry.ts) — keep both in lockstep.
if (conn.sessions.get(sessionId)?.stream === stream) { if (conn.sessions.get(sessionId)?.stream === stream) {
conn.sessions.get(sessionId)?.promptAbort?.abort(); conn.sessions.get(sessionId)?.promptAbort?.abort();
} }
@ -799,7 +799,7 @@ function headerOf(req: Request, name: string): string | undefined {
* True when the request's KERNEL-stamped peer address is loopback. Mirrors * True when the request's KERNEL-stamped peer address is loopback. Mirrors
* the REST surface's `detectFromLoopback` (NOT derived from forgeable * the REST surface's `detectFromLoopback` (NOT derived from forgeable
* headers like `X-Forwarded-For`). Replicated here rather than imported * headers like `X-Forwarded-For`). Replicated here rather than imported
* from `server.ts` to avoid a serveracpHttp import cycle. * from `server.ts` to avoid a serveracp-http import cycle.
*/ */
function isLoopbackSocket(socket: Duplex): boolean { function isLoopbackSocket(socket: Duplex): boolean {
const addr = (socket as unknown as { remoteAddress?: string }).remoteAddress; const addr = (socket as unknown as { remoteAddress?: string }).remoteAddress;

View file

@ -12,9 +12,9 @@ import {
parseInbound, parseInbound,
QWEN_METHOD_NS, QWEN_METHOD_NS,
RPC, RPC,
} from './jsonRpc.js'; } from './json-rpc.js';
describe('jsonRpc helpers', () => { describe('json-rpc helpers', () => {
it('classifies a request', () => { it('classifies a request', () => {
const m = { jsonrpc: '2.0', id: 1, method: 'initialize' }; const m = { jsonrpc: '2.0', id: 1, method: 'initialize' };
expect(isRequest(m)).toBe(true); expect(isRequest(m)).toBe(true);

View file

@ -6,7 +6,7 @@
/** /**
* Minimal JSON-RPC 2.0 helpers for the ACP-over-HTTP transport * Minimal JSON-RPC 2.0 helpers for the ACP-over-HTTP transport
* (`packages/cli/src/serve/acpHttp/`). The official ACP Streamable HTTP * (`packages/cli/src/serve/acp-http/`). The official ACP Streamable HTTP
* transport (RFD #721) frames every message as a JSON-RPC 2.0 object; * transport (RFD #721) frames every message as a JSON-RPC 2.0 object;
* this module owns the wire types + parse/validate/serialize so the * this module owns the wire types + parse/validate/serialize so the
* dispatcher stays focused on bridge routing. * dispatcher stays focused on bridge routing.

View file

@ -7,7 +7,7 @@
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { afterEach, describe, expect, it, vi } from 'vitest'; import { afterEach, describe, expect, it, vi } from 'vitest';
import type { Response } from 'express'; import type { Response } from 'express';
import { SseStream } from './sseStream.js'; import { SseStream } from './sse-stream.js';
/** /**
* Minimal Express `Response` mock: an EventEmitter with the `write`/`end`/ * Minimal Express `Response` mock: an EventEmitter with the `write`/`end`/

View file

@ -6,7 +6,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { WsStream } from './wsStream.js'; import { WsStream } from './ws-stream.js';
// Minimal WebSocket mock that implements the surface WsStream uses. // Minimal WebSocket mock that implements the surface WsStream uses.
class MockWebSocket extends EventEmitter { class MockWebSocket extends EventEmitter {

View file

@ -6,7 +6,7 @@
import type { WebSocket } from 'ws'; import type { WebSocket } from 'ws';
import { writeStderrLine } from '../../utils/stdioHelpers.js'; import { writeStderrLine } from '../../utils/stdioHelpers.js';
import type { TransportStream } from './transportStream.js'; import type { TransportStream } from './transport-stream.js';
export class WsStream implements TransportStream { export class WsStream implements TransportStream {
readonly kind = 'ws' as const; readonly kind = 'ws' as const;

View file

@ -10,10 +10,10 @@
* #4175 PR F1 lifted the bridge core (`BridgeClient`, * #4175 PR F1 lifted the bridge core (`BridgeClient`,
* `defaultSpawnChannelFactory`, `createAcpSessionBridge` factory closure, * `defaultSpawnChannelFactory`, `createAcpSessionBridge` factory closure,
* plus the supporting types/errors/options/status) to * plus the supporting types/errors/options/status) to
* `@qwen-code/acp-bridge`. This shim preserves every existing relative * `@qwen-code/acp-bridge`. This shim preserves the CLI-local bridge import
* import path (`./acpSessionBridge.js`) so `server.ts`, `run-qwen-serve.ts`, * surface so `server.ts`, `run-qwen-serve.ts`, `workspace-agents.ts`,
* `workspace-agents.ts`, `workspace-memory.ts`, `index.ts`, plus the * `workspace-memory.ts`, `index.ts`, plus the bridge test suite, keep resolving
* bridge test suite, keep resolving without any call-site changes. * through one module.
* *
* The implementation now lives at: * The implementation now lives at:
* - `@qwen-code/acp-bridge/bridge` `createAcpSessionBridge` factory * - `@qwen-code/acp-bridge/bridge` `createAcpSessionBridge` factory

View file

@ -16,7 +16,7 @@ import {
lstatSync, lstatSync,
} from 'node:fs'; } from 'node:fs';
import { describe, it, expect, afterEach, beforeEach } from 'vitest'; import { describe, it, expect, afterEach, beforeEach } from 'vitest';
import { buildDaemonLogLine, initDaemonLogger } from './daemonLogger.js'; import { buildDaemonLogLine, initDaemonLogger } from './daemon-logger.js';
describe('buildDaemonLogLine', () => { describe('buildDaemonLogLine', () => {
const FIXED = new Date('2026-05-26T03:14:15.926Z'); const FIXED = new Date('2026-05-26T03:14:15.926Z');

View file

@ -6,16 +6,16 @@
import type { RequestHandler } from 'express'; import type { RequestHandler } from 'express';
import { afterEach, describe, expect, it, vi } from 'vitest'; import { afterEach, describe, expect, it, vi } from 'vitest';
import type { AcpHttpHandle } from './acpHttp/index.js'; import type { AcpHttpHandle } from './acp-http/index.js';
import type { import type {
AcpSessionBridge, AcpSessionBridge,
BridgeDaemonStatusSnapshot, BridgeDaemonStatusSnapshot,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import { DeviceFlowRegistry } from './auth/device-flow.js'; import { DeviceFlowRegistry } from './auth/device-flow.js';
import { import {
buildDaemonStatusResponse, buildDaemonStatusResponse,
type BuildDaemonStatusOptions, type BuildDaemonStatusOptions,
} from './daemonStatus.js'; } from './daemon-status.js';
import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js'; import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js';
import type { DaemonWorkspaceService } from './workspace-service/index.js'; import type { DaemonWorkspaceService } from './workspace-service/index.js';

View file

@ -5,13 +5,13 @@
*/ */
import type { ServeProtocolVersions } from './capabilities.js'; import type { ServeProtocolVersions } from './capabilities.js';
import type { AcpHttpHandle } from './acpHttp/index.js'; import type { AcpHttpHandle } from './acp-http/index.js';
import type { DeviceFlowRegistry } from './auth/device-flow.js'; import type { DeviceFlowRegistry } from './auth/device-flow.js';
import type { DaemonLogger } from './daemonLogger.js'; import type { DaemonLogger } from './daemon-logger.js';
import type { import type {
AcpSessionBridge, AcpSessionBridge,
BridgeDaemonStatusSnapshot, BridgeDaemonStatusSnapshot,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import { isLoopbackBind } from './loopback-binds.js'; import { isLoopbackBind } from './loopback-binds.js';
import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js'; import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js';
import type { ServeOptions } from './types.js'; import type { ServeOptions } from './types.js';

View file

@ -116,7 +116,7 @@ export {
// embeds that want to recognize these errors (parallel to how // embeds that want to recognize these errors (parallel to how
// they already match `WorkspaceInitConflictError` / // they already match `WorkspaceInitConflictError` /
// `SessionNotFoundError`) need them on the public barrel; without // `SessionNotFoundError`) need them on the public barrel; without
// this they have to deep-import `./acpSessionBridge.js`. // this they have to deep-import `./acp-session-bridge.js`.
McpServerNotFoundError, McpServerNotFoundError,
McpServerRestartFailedError, McpServerRestartFailedError,
SessionNotFoundError, SessionNotFoundError,
@ -133,7 +133,7 @@ export {
type BridgeSpawnRequest, type BridgeSpawnRequest,
type ChannelFactory, type ChannelFactory,
type HttpAcpBridge, type HttpAcpBridge,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
export { export {
EventBus, EventBus,
EVENT_SCHEMA_VERSION, EVENT_SCHEMA_VERSION,

View file

@ -77,7 +77,7 @@ import {
type A2uiActionResult, type A2uiActionResult,
type McpServerCell, type McpServerCell,
type McpServerConfigLike, type McpServerConfigLike,
} from './a2uiAction.js'; } from './a2ui-action.js';
function makeApp(opts: { function makeApp(opts: {
servers?: McpServerCell[]; servers?: McpServerCell[];

View file

@ -5,7 +5,7 @@
*/ */
import type { Application, Request, RequestHandler, Response } from 'express'; import type { Application, Request, RequestHandler, Response } from 'express';
import type { AcpSessionBridge } from '../acpSessionBridge.js'; import type { AcpSessionBridge } from '../acp-session-bridge.js';
import { import {
isContentHash, isContentHash,
type ContentHash, type ContentHash,

View file

@ -14,7 +14,7 @@ import {
runQwenServe, runQwenServe,
validatePolicyConfig, validatePolicyConfig,
} from './run-qwen-serve.js'; } from './run-qwen-serve.js';
import type { HttpAcpBridge } from './acpSessionBridge.js'; import type { HttpAcpBridge } from './acp-session-bridge.js';
/** /**
* #4297 fold-in 7 (deepseek S1, addresses #3262690842). Lock the * #4297 fold-in 7 (deepseek S1, addresses #3262690842). Lock the

View file

@ -20,7 +20,7 @@ import {
canonicalizeWorkspace, canonicalizeWorkspace,
createAcpSessionBridge, createAcpSessionBridge,
type AcpSessionBridge, type AcpSessionBridge,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import { import {
DEFAULT_OTLP_ENDPOINT, DEFAULT_OTLP_ENDPOINT,
DEFAULT_TELEMETRY_TARGET, DEFAULT_TELEMETRY_TARGET,
@ -51,7 +51,7 @@ import {
import { createBridgeFileSystemAdapter } from './bridge-file-system-adapter.js'; import { createBridgeFileSystemAdapter } from './bridge-file-system-adapter.js';
import { createDaemonStatusProvider } from './daemon-status-provider.js'; import { createDaemonStatusProvider } from './daemon-status-provider.js';
import { isLoopbackBind } from './loopback-binds.js'; import { isLoopbackBind } from './loopback-binds.js';
import { resolveWebShellDir } from './webShellStatic.js'; import { resolveWebShellDir } from './web-shell-static.js';
import { parseAllowOriginPatterns } from './auth.js'; import { parseAllowOriginPatterns } from './auth.js';
import { import {
createPermissionAuditPublisher, createPermissionAuditPublisher,
@ -62,7 +62,7 @@ import {
getActiveSseCount, getActiveSseCount,
resolveBridgeFsFactory, resolveBridgeFsFactory,
} from './server.js'; } from './server.js';
import { initDaemonLogger, type DaemonLogger } from './daemonLogger.js'; import { initDaemonLogger, type DaemonLogger } from './daemon-logger.js';
import { createSpawnChannelFactory } from '@qwen-code/acp-bridge/spawnChannel'; import { createSpawnChannelFactory } from '@qwen-code/acp-bridge/spawnChannel';
import { createDaemonWorkspaceService } from './workspace-service/index.js'; import { createDaemonWorkspaceService } from './workspace-service/index.js';
import { SERVE_CAPABILITY_REGISTRY } from './capabilities.js'; import { SERVE_CAPABILITY_REGISTRY } from './capabilities.js';
@ -75,7 +75,7 @@ import type { WorkspaceFileSystemFactory } from './fs/index.js';
import type { PermissionPolicy } from '@qwen-code/acp-bridge'; import type { PermissionPolicy } from '@qwen-code/acp-bridge';
import { getCliVersion } from '../utils/version.js'; import { getCliVersion } from '../utils/version.js';
import { getRateLimiter } from './rate-limit.js'; import { getRateLimiter } from './rate-limit.js';
import type { AcpHttpHandle } from './acpHttp/index.js'; import type { AcpHttpHandle } from './acp-http/index.js';
const QWEN_SERVER_TOKEN_ENV = 'QWEN_SERVER_TOKEN'; const QWEN_SERVER_TOKEN_ENV = 'QWEN_SERVER_TOKEN';
const QWEN_SERVE_PROMPT_DEADLINE_MS_ENV = 'QWEN_SERVE_PROMPT_DEADLINE_MS'; const QWEN_SERVE_PROMPT_DEADLINE_MS_ENV = 'QWEN_SERVE_PROMPT_DEADLINE_MS';

View file

@ -20,7 +20,10 @@ import {
resolvePromptDeadlineMs, resolvePromptDeadlineMs,
} from './server.js'; } from './server.js';
import { runQwenServe, type RunHandle } from './run-qwen-serve.js'; import { runQwenServe, type RunHandle } from './run-qwen-serve.js';
import { resolveWebShellDir, isDocumentNavigation } from './webShellStatic.js'; import {
resolveWebShellDir,
isDocumentNavigation,
} from './web-shell-static.js';
import { import {
CONDITIONAL_SERVE_FEATURES, CONDITIONAL_SERVE_FEATURES,
getAdvertisedServeFeatures, getAdvertisedServeFeatures,
@ -77,7 +80,7 @@ import {
type BridgeSpawnRequest, type BridgeSpawnRequest,
type AcpSessionBridge, type AcpSessionBridge,
type SessionMetadataUpdate, type SessionMetadataUpdate,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import type { BridgeEvent, SubscribeOptions } from './event-bus.js'; import type { BridgeEvent, SubscribeOptions } from './event-bus.js';
import type { import type {
ServeSessionContextStatus, ServeSessionContextStatus,
@ -96,7 +99,7 @@ import type {
ServeWorkspaceToolsStatus, ServeWorkspaceToolsStatus,
} from './status.js'; } from './status.js';
import { CAPABILITIES_SCHEMA_VERSION, type ServeOptions } from './types.js'; import { CAPABILITIES_SCHEMA_VERSION, type ServeOptions } from './types.js';
import type { DaemonLogger } from './daemonLogger.js'; import type { DaemonLogger } from './daemon-logger.js';
import { FsError, type WorkspaceFileSystemFactory } from './fs/index.js'; import { FsError, type WorkspaceFileSystemFactory } from './fs/index.js';
const baseOpts: ServeOptions = { const baseOpts: ServeOptions = {
@ -12229,7 +12232,7 @@ describe('sendBridgeError daemonLog routing', () => {
it('routes 5xx errors through daemonLog when provided', async () => { it('routes 5xx errors through daemonLog when provided', async () => {
const tmp = await fsp.mkdtemp(path.join(os.tmpdir(), 'daemon-log-')); const tmp = await fsp.mkdtemp(path.join(os.tmpdir(), 'daemon-log-'));
const stderrLines: string[] = []; const stderrLines: string[] = [];
const { initDaemonLogger } = await import('./daemonLogger.js'); const { initDaemonLogger } = await import('./daemon-logger.js');
const daemonLog = initDaemonLogger({ const daemonLog = initDaemonLogger({
boundWorkspace: '/w', boundWorkspace: '/w',
pid: 1, pid: 1,

View file

@ -36,7 +36,7 @@ import {
type ExtensionSetting, type ExtensionSetting,
} from '@qwen-code/qwen-code-core'; } from '@qwen-code/qwen-code-core';
import { writeStderrLine } from '../utils/stdioHelpers.js'; import { writeStderrLine } from '../utils/stdioHelpers.js';
import type { DaemonLogger } from './daemonLogger.js'; import type { DaemonLogger } from './daemon-logger.js';
import { import {
allowOriginCors, allowOriginCors,
bearerAuth, bearerAuth,
@ -65,11 +65,11 @@ import { SUPPORTED_LANGUAGES } from '../i18n/index.js';
import { loadSettings } from '../config/settings.js'; import { loadSettings } from '../config/settings.js';
import { isWorkspaceTrusted } from '../config/trustedFolders.js'; import { isWorkspaceTrusted } from '../config/trustedFolders.js';
import { isLoopbackBind } from './loopback-binds.js'; import { isLoopbackBind } from './loopback-binds.js';
import { mountAcpHttp, type AcpHttpHandle } from './acpHttp/index.js'; import { mountAcpHttp, type AcpHttpHandle } from './acp-http/index.js';
import { import {
buildDaemonStatusResponse, buildDaemonStatusResponse,
parseDaemonStatusDetail, parseDaemonStatusDetail,
} from './daemonStatus.js'; } from './daemon-status.js';
import { import {
canonicalizeWorkspace, canonicalizeWorkspace,
CancelSentinelCollisionError, CancelSentinelCollisionError,
@ -99,7 +99,7 @@ import {
WorkspaceMismatchError, WorkspaceMismatchError,
type BridgeSessionSummary, type BridgeSessionSummary,
type AcpSessionBridge, type AcpSessionBridge,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import { import {
getAdvertisedServeFeatures, getAdvertisedServeFeatures,
getServeProtocolVersions, getServeProtocolVersions,
@ -118,7 +118,7 @@ import { getDemoHtml } from './demo.js';
import { import {
mountWebShellAssets, mountWebShellAssets,
mountWebShellSpaFallback, mountWebShellSpaFallback,
} from './webShellStatic.js'; } from './web-shell-static.js';
import { mountWorkspaceMemoryRoutes } from './workspace-memory.js'; import { mountWorkspaceMemoryRoutes } from './workspace-memory.js';
import { mountWorkspaceAgentsRoutes } from './workspace-agents.js'; import { mountWorkspaceAgentsRoutes } from './workspace-agents.js';
import { import {
@ -133,7 +133,7 @@ import {
type WorkspaceRequestContext, type WorkspaceRequestContext,
} from './workspace-service/index.js'; } from './workspace-service/index.js';
import { registerWorkspaceSettingsRoutes } from './routes/workspace-settings.js'; import { registerWorkspaceSettingsRoutes } from './routes/workspace-settings.js';
import { registerA2uiActionRoutes } from './routes/a2uiAction.js'; import { registerA2uiActionRoutes } from './routes/a2ui-action.js';
import { import {
createRateLimiter, createRateLimiter,
setRateLimiter, setRateLimiter,

View file

@ -20,7 +20,7 @@ import {
} from 'vitest'; } from 'vitest';
import { Storage, QWEN_DIR } from '@qwen-code/qwen-code-core'; import { Storage, QWEN_DIR } from '@qwen-code/qwen-code-core';
import { createMutationGate } from './auth.js'; import { createMutationGate } from './auth.js';
import type { AcpSessionBridge } from './acpSessionBridge.js'; import type { AcpSessionBridge } from './acp-session-bridge.js';
import type { BridgeEvent } from './event-bus.js'; import type { BridgeEvent } from './event-bus.js';
import { mountWorkspaceAgentsRoutes } from './workspace-agents.js'; import { mountWorkspaceAgentsRoutes } from './workspace-agents.js';

View file

@ -21,7 +21,7 @@ import { isServeDebugMode } from './debug-mode.js';
import { import {
InvalidClientIdError, InvalidClientIdError,
type AcpSessionBridge, type AcpSessionBridge,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
/** /**
* Pattern for the route-layer `:agentType` URL parameter. Matches the * Pattern for the route-layer `:agentType` URL parameter. Matches the
@ -1370,5 +1370,5 @@ export function createDaemonSubagentManager(
// Re-export the bridge error type used by route helpers so test files // Re-export the bridge error type used by route helpers so test files
// can import it from a single module without reaching into // can import it from a single module without reaching into
// acpSessionBridge directly. // acp-session-bridge directly.
export { InvalidClientIdError }; export { InvalidClientIdError };

View file

@ -23,7 +23,7 @@ import { createMutationGate } from './auth.js';
import { import {
InvalidClientIdError, InvalidClientIdError,
type AcpSessionBridge, type AcpSessionBridge,
} from './acpSessionBridge.js'; } from './acp-session-bridge.js';
import type { BridgeEvent } from './event-bus.js'; import type { BridgeEvent } from './event-bus.js';
import { mountWorkspaceMemoryRoutes } from './workspace-memory.js'; import { mountWorkspaceMemoryRoutes } from './workspace-memory.js';

View file

@ -16,7 +16,7 @@ import {
} from '@qwen-code/qwen-code-core'; } from '@qwen-code/qwen-code-core';
import { writeStderrLine } from '../utils/stdioHelpers.js'; import { writeStderrLine } from '../utils/stdioHelpers.js';
import { isServeDebugMode } from './debug-mode.js'; import { isServeDebugMode } from './debug-mode.js';
import type { AcpSessionBridge } from './acpSessionBridge.js'; import type { AcpSessionBridge } from './acp-session-bridge.js';
import { import {
createIdleWorkspaceMemoryStatus, createIdleWorkspaceMemoryStatus,
STATUS_SCHEMA_VERSION, STATUS_SCHEMA_VERSION,

View file

@ -20,7 +20,7 @@ import type {
DaemonWorkspaceService, DaemonWorkspaceService,
WorkspaceRequestContext, WorkspaceRequestContext,
} from '../types.js'; } from '../types.js';
import type { AcpSessionBridge } from '../../acpSessionBridge.js'; import type { AcpSessionBridge } from '../../acp-session-bridge.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Test fixtures // Test fixtures