diff --git a/docs/design/daemon-acp-http/README.md b/docs/design/daemon-acp-http/README.md index f0762c74e7..3fbcb39f45 100644 --- a/docs/design/daemon-acp-http/README.md +++ b/docs/design/daemon-acp-http/README.md @@ -25,7 +25,7 @@ standard ACP method (model switch, workspace introspection, heartbeat, multi-client permission policy, SSE backpressure tuning). Rationale in §5. 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`). --- @@ -151,15 +151,15 @@ client ──────────────────────── 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 | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `index.ts` | `mountAcpHttp(app, bridge, opts)` — registers `/acp` routes on the existing Express app. | -| `connectionRegistry.ts` | `Acp-Connection-Id` → `AcpConnection` (connection SSE writer, `Map`, 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. | -| `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). | +| File | Responsibility | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `index.ts` | `mountAcpHttp(app, bridge, opts)` — registers `/acp` routes on the existing Express app. | +| `connection-registry.ts` | `Acp-Connection-Id` → `AcpConnection` (connection SSE writer, `Map`, pending agent→client requests by JSON-RPC id, monotonic id allocator). TTL + DELETE cleanup. | +| `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). | +| `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). @@ -284,7 +284,7 @@ thin compat shim over `/acp` (separate, later PR). - `session/request_permission` agent→client round-trip. - `_qwen/session/set_model` extension as the worked example of #2. - 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):** @@ -309,7 +309,7 @@ thin compat shim over `/acp` (separate, later PR). - Trigger a tool needing permission → assert `session/request_permission` request, POST a grant response → assert prompt completes. - `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) -Implemented in `packages/cli/src/serve/acpHttp/` (`jsonRpc.ts`, `sseStream.ts`, -`connectionRegistry.ts`, `dispatch.ts`, `index.ts`), mounted from `server.ts` +Implemented in `packages/cli/src/serve/acp-http/` (`json-rpc.ts`, `sse-stream.ts`, +`connection-registry.ts`, `dispatch.ts`, `index.ts`), mounted from `server.ts` 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 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. | | 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). | -| F6 | **P2** | `isObjectParams` duplicated `jsonRpc.isObject`. | Import `isObject`. | -| F7 | **P2** | Raw `process.stderr.write` in `index.ts`/`sseStream.ts` vs `writeStderrLine` elsewhere. | Unified on `writeStderrLine` across the module. | +| F6 | **P2** | `isObjectParams` duplicated `json-rpc.isObject`. | Import `isObject`. | +| F7 | **P2** | Raw `process.stderr.write` in `index.ts`/`sse-stream.ts` vs `writeStderrLine` elsewhere. | Unified on `writeStderrLine` across the module. | --- diff --git a/docs/developers/daemon/02-serve-runtime.md b/docs/developers/daemon/02-serve-runtime.md index 91c9840287..f7dca2879d 100644 --- a/docs/developers/daemon/02-serve-runtime.md +++ b/docs/developers/daemon/02-serve-runtime.md @@ -37,19 +37,19 @@ **Subsystems**: -| 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. | +| 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/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-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/permission-audit.ts` | `PermissionAuditRing` (512-entry FIFO) and `createPermissionAuditPublisher`. | +| `serve/workspace-memory.ts` | `GET/POST /workspace/memory` (QWEN.md 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/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/daemonLogger.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/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/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/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/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`. | **Re-export shims** for compatibility with pre-F1 import paths: diff --git a/docs/developers/daemon/19-observability.md b/docs/developers/daemon/19-observability.md index a0037da55d..b1c8a01b86 100644 --- a/docs/developers/daemon/19-observability.md +++ b/docs/developers/daemon/19-observability.md @@ -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. | | 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 -> `. 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 -> `. 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`. | | `/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). | @@ -20,7 +20,7 @@ | `/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. | | `/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. | ## What does not exist today @@ -143,7 +143,7 @@ flowchart TD ## References - `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/acp-bridge/src/permissionMediator.ts` (`PermissionDecisionReason`) - `packages/cli/src/serve/server.ts` (`daemonTelemetryMiddleware`, access-log middleware) diff --git a/eslint.legacy-filenames.mjs b/eslint.legacy-filenames.mjs index ea111e7ad5..3480630a12 100644 --- a/eslint.legacy-filenames.mjs +++ b/eslint.legacy-filenames.mjs @@ -82,17 +82,13 @@ export const legacyFilenames = [ 'cronScheduler', 'customBanner', 'customMatchers', - 'daemonStatusProvider', - 'DaemonTuiAdapter', 'dangerousRules', 'DataProcessor', 'debugLogger', - 'debugMode', 'deepMerge', 'deleteCommand', 'denialTracking', 'detectTodoChanges', - 'deviceFlow', 'dialogScopeUtils', 'diffCommand', 'diffOptions', @@ -109,7 +105,6 @@ export const legacyFilenames = [ 'editorSettingsManager', 'envInterpolator', 'environmentContext', - 'envSnapshot', 'envVarResolver', 'errorCodes', 'errorHandler', @@ -205,7 +200,6 @@ export const legacyFilenames = [ 'LlmRewriter', 'loadedSettingsAdapter', 'loggingContentGenerator', - 'loopbackBinds', 'loopDetectionService', 'lowlightLoader', 'LruCache', @@ -283,7 +277,6 @@ export const legacyFilenames = [ 'quitCommand', 'quotaErrorDetection', 'qwenContentGenerator', - 'qwenDeviceFlowProvider', 'qwenIgnoreParser', 'qwenOAuth2', 'rateLimit', @@ -309,7 +302,6 @@ export const legacyFilenames = [ 'ripgrepUtils', 'rulesDiscovery', 'runBudget', - 'runQwenServe', 'runtimeDiagnostics', 'runtimeFetchOptions', 'runtimeOutputDirContext', @@ -511,12 +503,7 @@ export const legacyFilenames = [ 'warningHandler', 'windowsPath', 'windowTitle', - 'workspaceAgents', 'workspaceContext', - 'workspaceFileRead', - 'workspaceFileSystem', - 'workspaceFileWrite', - 'workspaceMemory', 'worktreeCleanup', 'worktreeSessionService', 'worktreeStartup', @@ -525,10 +512,7 @@ export const legacyFilenames = [ 'CodeColorizer', 'SKILL', 'TeamManager', - 'a2uiAction', - 'acpSessionBridge', 'asciiCharts', - 'bridgeFileSystemAdapter', 'btwUtils', 'cdCommand', 'claudeMcpImport', @@ -537,12 +521,9 @@ export const legacyFilenames = [ 'compressFastCommand', 'concurrencyLimiter', 'configHash', - 'connectionRegistry', 'corruptFile', 'cronTasksFile', 'cronTasksLock', - 'daemonLogger', - 'daemonStatus', 'enterPlanMode', 'extensionPreferences', 'forkCommand', @@ -555,7 +536,6 @@ export const legacyFilenames = [ 'importConfigCommand', 'inlineMediaLimit', 'instructionsLoadedCallback', - 'jsonRpc', 'leaderPermissionBridge', 'liveAgentPanelVisibility', 'mcpApprovals', @@ -564,7 +544,6 @@ export const legacyFilenames = [ 'mcpServers', 'midTurnUserMessage', 'normalizeDisabledTools', - 'permissionAudit', 'planApprovalGate', 'promptAddendum', 'retryErrorClassification', @@ -578,7 +557,6 @@ export const legacyFilenames = [ 'settingsWatcher', 'sleepInhibitor', 'sourceRegistry', - 'sseStream', 'statsDataService', 'tasksSnapshot', 'teamHelpers', @@ -587,15 +565,11 @@ export const legacyFilenames = [ 'toolCallIdUtils', 'toolResultCleanup', 'toolResultDisplayCompaction', - 'transportStream', 'usageHistoryService', 'useMcpApproval', 'useResizeSettleRepaint', 'useStatsDialog', 'useTeamInProcess', 'userMemory', - 'webShellStatic', 'workflowsCommand', - 'workspaceSettings', - 'wsStream', ]; diff --git a/packages/cli/src/serve/acpHttp/connectionRegistry.test.ts b/packages/cli/src/serve/acp-http/connection-registry.test.ts similarity index 95% rename from packages/cli/src/serve/acpHttp/connectionRegistry.test.ts rename to packages/cli/src/serve/acp-http/connection-registry.test.ts index a00bcd55fa..e35d1e9282 100644 --- a/packages/cli/src/serve/acpHttp/connectionRegistry.test.ts +++ b/packages/cli/src/serve/acp-http/connection-registry.test.ts @@ -5,8 +5,8 @@ */ import { describe, expect, it } from 'vitest'; -import { ConnectionRegistry } from './connectionRegistry.js'; -import type { TransportStream } from './transportStream.js'; +import { ConnectionRegistry } from './connection-registry.js'; +import type { TransportStream } from './transport-stream.js'; class FakeStream implements TransportStream { isClosed = false; diff --git a/packages/cli/src/serve/acpHttp/connectionRegistry.ts b/packages/cli/src/serve/acp-http/connection-registry.ts similarity index 99% rename from packages/cli/src/serve/acpHttp/connectionRegistry.ts rename to packages/cli/src/serve/acp-http/connection-registry.ts index 67dd453418..5e387386a0 100644 --- a/packages/cli/src/serve/acpHttp/connectionRegistry.ts +++ b/packages/cli/src/serve/acp-http/connection-registry.ts @@ -6,8 +6,8 @@ import { randomUUID } from 'node:crypto'; import { writeStderrLine } from '../../utils/stdioHelpers.js'; -import { logSafe } from './jsonRpc.js'; -import type { TransportStream } from './transportStream.js'; +import { logSafe } from './json-rpc.js'; +import type { TransportStream } from './transport-stream.js'; /** * Per-stream cap on frames buffered before the client attaches its SSE diff --git a/packages/cli/src/serve/acpHttp/dispatch.ts b/packages/cli/src/serve/acp-http/dispatch.ts similarity index 99% rename from packages/cli/src/serve/acpHttp/dispatch.ts rename to packages/cli/src/serve/acp-http/dispatch.ts index 1843044da2..1d2cb63984 100644 --- a/packages/cli/src/serve/acpHttp/dispatch.ts +++ b/packages/cli/src/serve/acp-http/dispatch.ts @@ -50,7 +50,7 @@ import type { DaemonWorkspaceService, WorkspaceRequestContext, } from '../workspace-service/types.js'; -import type { AcpConnection } from './connectionRegistry.js'; +import type { AcpConnection } from './connection-registry.js'; import { QWEN_META_KEY, QWEN_METHOD_NS, @@ -68,7 +68,7 @@ import { type JsonRpcInbound, type JsonRpcRequest, type JsonRpcResponse, -} from './jsonRpc.js'; +} from './json-rpc.js'; function errMsg(err: unknown): string { 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 }; diff --git a/packages/cli/src/serve/acpHttp/index.ts b/packages/cli/src/serve/acp-http/index.ts similarity index 99% rename from packages/cli/src/serve/acpHttp/index.ts rename to packages/cli/src/serve/acp-http/index.ts index 9c2a43a709..97d93237e1 100644 --- a/packages/cli/src/serve/acpHttp/index.ts +++ b/packages/cli/src/serve/acp-http/index.ts @@ -18,11 +18,11 @@ import { AcpDispatcher } from './dispatch.js'; import { ConnectionRegistry, type AcpConnection, -} from './connectionRegistry.js'; -import { SseStream } from './sseStream.js'; -import { WsStream } from './wsStream.js'; +} from './connection-registry.js'; +import { SseStream } from './sse-stream.js'; +import { WsStream } from './ws-stream.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_SESSION_HEADER = 'acp-session-id'; @@ -362,7 +362,7 @@ export function mountAcpHttp( // stream. A reconnect already installed a newer stream — the prompt // must survive the old stream's close. CONTRACT: this identity guard // 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) { 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 * the REST surface's `detectFromLoopback` (NOT derived from forgeable * headers like `X-Forwarded-For`). Replicated here rather than imported - * from `server.ts` to avoid a server↔acpHttp import cycle. + * from `server.ts` to avoid a server↔acp-http import cycle. */ function isLoopbackSocket(socket: Duplex): boolean { const addr = (socket as unknown as { remoteAddress?: string }).remoteAddress; diff --git a/packages/cli/src/serve/acpHttp/jsonRpc.test.ts b/packages/cli/src/serve/acp-http/json-rpc.test.ts similarity index 97% rename from packages/cli/src/serve/acpHttp/jsonRpc.test.ts rename to packages/cli/src/serve/acp-http/json-rpc.test.ts index 43281887ad..b93907db80 100644 --- a/packages/cli/src/serve/acpHttp/jsonRpc.test.ts +++ b/packages/cli/src/serve/acp-http/json-rpc.test.ts @@ -12,9 +12,9 @@ import { parseInbound, QWEN_METHOD_NS, RPC, -} from './jsonRpc.js'; +} from './json-rpc.js'; -describe('jsonRpc helpers', () => { +describe('json-rpc helpers', () => { it('classifies a request', () => { const m = { jsonrpc: '2.0', id: 1, method: 'initialize' }; expect(isRequest(m)).toBe(true); diff --git a/packages/cli/src/serve/acpHttp/jsonRpc.ts b/packages/cli/src/serve/acp-http/json-rpc.ts similarity index 98% rename from packages/cli/src/serve/acpHttp/jsonRpc.ts rename to packages/cli/src/serve/acp-http/json-rpc.ts index ef75579888..b0c32664f4 100644 --- a/packages/cli/src/serve/acpHttp/jsonRpc.ts +++ b/packages/cli/src/serve/acp-http/json-rpc.ts @@ -6,7 +6,7 @@ /** * 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; * this module owns the wire types + parse/validate/serialize so the * dispatcher stays focused on bridge routing. diff --git a/packages/cli/src/serve/acpHttp/sseStream.test.ts b/packages/cli/src/serve/acp-http/sse-stream.test.ts similarity index 98% rename from packages/cli/src/serve/acpHttp/sseStream.test.ts rename to packages/cli/src/serve/acp-http/sse-stream.test.ts index 500fe27072..fe125d8390 100644 --- a/packages/cli/src/serve/acpHttp/sseStream.test.ts +++ b/packages/cli/src/serve/acp-http/sse-stream.test.ts @@ -7,7 +7,7 @@ import { EventEmitter } from 'node:events'; import { afterEach, describe, expect, it, vi } from 'vitest'; 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`/ diff --git a/packages/cli/src/serve/acpHttp/sseStream.ts b/packages/cli/src/serve/acp-http/sse-stream.ts similarity index 100% rename from packages/cli/src/serve/acpHttp/sseStream.ts rename to packages/cli/src/serve/acp-http/sse-stream.ts diff --git a/packages/cli/src/serve/acpHttp/transportStream.ts b/packages/cli/src/serve/acp-http/transport-stream.ts similarity index 100% rename from packages/cli/src/serve/acpHttp/transportStream.ts rename to packages/cli/src/serve/acp-http/transport-stream.ts diff --git a/packages/cli/src/serve/acpHttp/transport.test.ts b/packages/cli/src/serve/acp-http/transport.test.ts similarity index 100% rename from packages/cli/src/serve/acpHttp/transport.test.ts rename to packages/cli/src/serve/acp-http/transport.test.ts diff --git a/packages/cli/src/serve/acpHttp/wsStream.test.ts b/packages/cli/src/serve/acp-http/ws-stream.test.ts similarity index 99% rename from packages/cli/src/serve/acpHttp/wsStream.test.ts rename to packages/cli/src/serve/acp-http/ws-stream.test.ts index 1df504ff69..fd1afb1783 100644 --- a/packages/cli/src/serve/acpHttp/wsStream.test.ts +++ b/packages/cli/src/serve/acp-http/ws-stream.test.ts @@ -6,7 +6,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; 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. class MockWebSocket extends EventEmitter { diff --git a/packages/cli/src/serve/acpHttp/wsStream.ts b/packages/cli/src/serve/acp-http/ws-stream.ts similarity index 97% rename from packages/cli/src/serve/acpHttp/wsStream.ts rename to packages/cli/src/serve/acp-http/ws-stream.ts index 6671899641..b62992cd38 100644 --- a/packages/cli/src/serve/acpHttp/wsStream.ts +++ b/packages/cli/src/serve/acp-http/ws-stream.ts @@ -6,7 +6,7 @@ import type { WebSocket } from 'ws'; import { writeStderrLine } from '../../utils/stdioHelpers.js'; -import type { TransportStream } from './transportStream.js'; +import type { TransportStream } from './transport-stream.js'; export class WsStream implements TransportStream { readonly kind = 'ws' as const; diff --git a/packages/cli/src/serve/acpSessionBridge.ts b/packages/cli/src/serve/acp-session-bridge.ts similarity index 92% rename from packages/cli/src/serve/acpSessionBridge.ts rename to packages/cli/src/serve/acp-session-bridge.ts index b249a520b9..0f9c232a30 100644 --- a/packages/cli/src/serve/acpSessionBridge.ts +++ b/packages/cli/src/serve/acp-session-bridge.ts @@ -10,10 +10,10 @@ * #4175 PR F1 lifted the bridge core (`BridgeClient`, * `defaultSpawnChannelFactory`, `createAcpSessionBridge` factory closure, * plus the supporting types/errors/options/status) to - * `@qwen-code/acp-bridge`. This shim preserves every existing relative - * import path (`./acpSessionBridge.js`) so `server.ts`, `run-qwen-serve.ts`, - * `workspace-agents.ts`, `workspace-memory.ts`, `index.ts`, plus the - * bridge test suite, keep resolving without any call-site changes. + * `@qwen-code/acp-bridge`. This shim preserves the CLI-local bridge import + * surface so `server.ts`, `run-qwen-serve.ts`, `workspace-agents.ts`, + * `workspace-memory.ts`, `index.ts`, plus the bridge test suite, keep resolving + * through one module. * * The implementation now lives at: * - `@qwen-code/acp-bridge/bridge` — `createAcpSessionBridge` factory diff --git a/packages/cli/src/serve/daemonLogger.test.ts b/packages/cli/src/serve/daemon-logger.test.ts similarity index 99% rename from packages/cli/src/serve/daemonLogger.test.ts rename to packages/cli/src/serve/daemon-logger.test.ts index 468c3d02c7..8c68160675 100644 --- a/packages/cli/src/serve/daemonLogger.test.ts +++ b/packages/cli/src/serve/daemon-logger.test.ts @@ -16,7 +16,7 @@ import { lstatSync, } from 'node:fs'; import { describe, it, expect, afterEach, beforeEach } from 'vitest'; -import { buildDaemonLogLine, initDaemonLogger } from './daemonLogger.js'; +import { buildDaemonLogLine, initDaemonLogger } from './daemon-logger.js'; describe('buildDaemonLogLine', () => { const FIXED = new Date('2026-05-26T03:14:15.926Z'); diff --git a/packages/cli/src/serve/daemonLogger.ts b/packages/cli/src/serve/daemon-logger.ts similarity index 100% rename from packages/cli/src/serve/daemonLogger.ts rename to packages/cli/src/serve/daemon-logger.ts diff --git a/packages/cli/src/serve/daemonStatus.test.ts b/packages/cli/src/serve/daemon-status.test.ts similarity index 98% rename from packages/cli/src/serve/daemonStatus.test.ts rename to packages/cli/src/serve/daemon-status.test.ts index 448d71b176..a6595b5263 100644 --- a/packages/cli/src/serve/daemonStatus.test.ts +++ b/packages/cli/src/serve/daemon-status.test.ts @@ -6,16 +6,16 @@ import type { RequestHandler } from 'express'; 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 { AcpSessionBridge, BridgeDaemonStatusSnapshot, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import { DeviceFlowRegistry } from './auth/device-flow.js'; import { buildDaemonStatusResponse, type BuildDaemonStatusOptions, -} from './daemonStatus.js'; +} from './daemon-status.js'; import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js'; import type { DaemonWorkspaceService } from './workspace-service/index.js'; diff --git a/packages/cli/src/serve/daemonStatus.ts b/packages/cli/src/serve/daemon-status.ts similarity index 99% rename from packages/cli/src/serve/daemonStatus.ts rename to packages/cli/src/serve/daemon-status.ts index 6d8b68f951..c241a4719c 100644 --- a/packages/cli/src/serve/daemonStatus.ts +++ b/packages/cli/src/serve/daemon-status.ts @@ -5,13 +5,13 @@ */ 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 { DaemonLogger } from './daemonLogger.js'; +import type { DaemonLogger } from './daemon-logger.js'; import type { AcpSessionBridge, BridgeDaemonStatusSnapshot, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import { isLoopbackBind } from './loopback-binds.js'; import type { RateLimiterInstance, RateLimitTier } from './rate-limit.js'; import type { ServeOptions } from './types.js'; diff --git a/packages/cli/src/serve/index.ts b/packages/cli/src/serve/index.ts index 27a0c4f438..586a5424e8 100644 --- a/packages/cli/src/serve/index.ts +++ b/packages/cli/src/serve/index.ts @@ -116,7 +116,7 @@ export { // embeds that want to recognize these errors (parallel to how // they already match `WorkspaceInitConflictError` / // `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, McpServerRestartFailedError, SessionNotFoundError, @@ -133,7 +133,7 @@ export { type BridgeSpawnRequest, type ChannelFactory, type HttpAcpBridge, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; export { EventBus, EVENT_SCHEMA_VERSION, diff --git a/packages/cli/src/serve/routes/a2uiAction.test.ts b/packages/cli/src/serve/routes/a2ui-action.test.ts similarity index 99% rename from packages/cli/src/serve/routes/a2uiAction.test.ts rename to packages/cli/src/serve/routes/a2ui-action.test.ts index f4fc500e64..c141a05373 100644 --- a/packages/cli/src/serve/routes/a2uiAction.test.ts +++ b/packages/cli/src/serve/routes/a2ui-action.test.ts @@ -77,7 +77,7 @@ import { type A2uiActionResult, type McpServerCell, type McpServerConfigLike, -} from './a2uiAction.js'; +} from './a2ui-action.js'; function makeApp(opts: { servers?: McpServerCell[]; diff --git a/packages/cli/src/serve/routes/a2uiAction.ts b/packages/cli/src/serve/routes/a2ui-action.ts similarity index 100% rename from packages/cli/src/serve/routes/a2uiAction.ts rename to packages/cli/src/serve/routes/a2ui-action.ts diff --git a/packages/cli/src/serve/routes/workspace-file-write.ts b/packages/cli/src/serve/routes/workspace-file-write.ts index b4214303e7..4fead58d0b 100644 --- a/packages/cli/src/serve/routes/workspace-file-write.ts +++ b/packages/cli/src/serve/routes/workspace-file-write.ts @@ -5,7 +5,7 @@ */ import type { Application, Request, RequestHandler, Response } from 'express'; -import type { AcpSessionBridge } from '../acpSessionBridge.js'; +import type { AcpSessionBridge } from '../acp-session-bridge.js'; import { isContentHash, type ContentHash, diff --git a/packages/cli/src/serve/run-qwen-serve.test.ts b/packages/cli/src/serve/run-qwen-serve.test.ts index 956ede8d21..ddb744c35e 100644 --- a/packages/cli/src/serve/run-qwen-serve.test.ts +++ b/packages/cli/src/serve/run-qwen-serve.test.ts @@ -14,7 +14,7 @@ import { runQwenServe, validatePolicyConfig, } 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 diff --git a/packages/cli/src/serve/run-qwen-serve.ts b/packages/cli/src/serve/run-qwen-serve.ts index 1c2b88a8c1..058c9a62c0 100644 --- a/packages/cli/src/serve/run-qwen-serve.ts +++ b/packages/cli/src/serve/run-qwen-serve.ts @@ -20,7 +20,7 @@ import { canonicalizeWorkspace, createAcpSessionBridge, type AcpSessionBridge, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import { DEFAULT_OTLP_ENDPOINT, DEFAULT_TELEMETRY_TARGET, @@ -51,7 +51,7 @@ import { import { createBridgeFileSystemAdapter } from './bridge-file-system-adapter.js'; import { createDaemonStatusProvider } from './daemon-status-provider.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 { createPermissionAuditPublisher, @@ -62,7 +62,7 @@ import { getActiveSseCount, resolveBridgeFsFactory, } 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 { createDaemonWorkspaceService } from './workspace-service/index.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 { getCliVersion } from '../utils/version.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_SERVE_PROMPT_DEADLINE_MS_ENV = 'QWEN_SERVE_PROMPT_DEADLINE_MS'; diff --git a/packages/cli/src/serve/server.test.ts b/packages/cli/src/serve/server.test.ts index d73d377416..72e0dae314 100644 --- a/packages/cli/src/serve/server.test.ts +++ b/packages/cli/src/serve/server.test.ts @@ -20,7 +20,10 @@ import { resolvePromptDeadlineMs, } from './server.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 { CONDITIONAL_SERVE_FEATURES, getAdvertisedServeFeatures, @@ -77,7 +80,7 @@ import { type BridgeSpawnRequest, type AcpSessionBridge, type SessionMetadataUpdate, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import type { BridgeEvent, SubscribeOptions } from './event-bus.js'; import type { ServeSessionContextStatus, @@ -96,7 +99,7 @@ import type { ServeWorkspaceToolsStatus, } from './status.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'; const baseOpts: ServeOptions = { @@ -12229,7 +12232,7 @@ describe('sendBridgeError daemonLog routing', () => { it('routes 5xx errors through daemonLog when provided', async () => { const tmp = await fsp.mkdtemp(path.join(os.tmpdir(), 'daemon-log-')); const stderrLines: string[] = []; - const { initDaemonLogger } = await import('./daemonLogger.js'); + const { initDaemonLogger } = await import('./daemon-logger.js'); const daemonLog = initDaemonLogger({ boundWorkspace: '/w', pid: 1, diff --git a/packages/cli/src/serve/server.ts b/packages/cli/src/serve/server.ts index e0cf66e8cb..9f999b6878 100644 --- a/packages/cli/src/serve/server.ts +++ b/packages/cli/src/serve/server.ts @@ -36,7 +36,7 @@ import { type ExtensionSetting, } from '@qwen-code/qwen-code-core'; import { writeStderrLine } from '../utils/stdioHelpers.js'; -import type { DaemonLogger } from './daemonLogger.js'; +import type { DaemonLogger } from './daemon-logger.js'; import { allowOriginCors, bearerAuth, @@ -65,11 +65,11 @@ import { SUPPORTED_LANGUAGES } from '../i18n/index.js'; import { loadSettings } from '../config/settings.js'; import { isWorkspaceTrusted } from '../config/trustedFolders.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 { buildDaemonStatusResponse, parseDaemonStatusDetail, -} from './daemonStatus.js'; +} from './daemon-status.js'; import { canonicalizeWorkspace, CancelSentinelCollisionError, @@ -99,7 +99,7 @@ import { WorkspaceMismatchError, type BridgeSessionSummary, type AcpSessionBridge, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import { getAdvertisedServeFeatures, getServeProtocolVersions, @@ -118,7 +118,7 @@ import { getDemoHtml } from './demo.js'; import { mountWebShellAssets, mountWebShellSpaFallback, -} from './webShellStatic.js'; +} from './web-shell-static.js'; import { mountWorkspaceMemoryRoutes } from './workspace-memory.js'; import { mountWorkspaceAgentsRoutes } from './workspace-agents.js'; import { @@ -133,7 +133,7 @@ import { type WorkspaceRequestContext, } from './workspace-service/index.js'; import { registerWorkspaceSettingsRoutes } from './routes/workspace-settings.js'; -import { registerA2uiActionRoutes } from './routes/a2uiAction.js'; +import { registerA2uiActionRoutes } from './routes/a2ui-action.js'; import { createRateLimiter, setRateLimiter, diff --git a/packages/cli/src/serve/webShellStatic.ts b/packages/cli/src/serve/web-shell-static.ts similarity index 100% rename from packages/cli/src/serve/webShellStatic.ts rename to packages/cli/src/serve/web-shell-static.ts diff --git a/packages/cli/src/serve/workspace-agents.test.ts b/packages/cli/src/serve/workspace-agents.test.ts index d07d28c56e..9026320c20 100644 --- a/packages/cli/src/serve/workspace-agents.test.ts +++ b/packages/cli/src/serve/workspace-agents.test.ts @@ -20,7 +20,7 @@ import { } from 'vitest'; import { Storage, QWEN_DIR } from '@qwen-code/qwen-code-core'; 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 { mountWorkspaceAgentsRoutes } from './workspace-agents.js'; diff --git a/packages/cli/src/serve/workspace-agents.ts b/packages/cli/src/serve/workspace-agents.ts index 07c88d9ecf..c287d8e7f0 100644 --- a/packages/cli/src/serve/workspace-agents.ts +++ b/packages/cli/src/serve/workspace-agents.ts @@ -21,7 +21,7 @@ import { isServeDebugMode } from './debug-mode.js'; import { InvalidClientIdError, type AcpSessionBridge, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; /** * 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 // can import it from a single module without reaching into -// acpSessionBridge directly. +// acp-session-bridge directly. export { InvalidClientIdError }; diff --git a/packages/cli/src/serve/workspace-memory.test.ts b/packages/cli/src/serve/workspace-memory.test.ts index 8b59a10275..e4dd34da3c 100644 --- a/packages/cli/src/serve/workspace-memory.test.ts +++ b/packages/cli/src/serve/workspace-memory.test.ts @@ -23,7 +23,7 @@ import { createMutationGate } from './auth.js'; import { InvalidClientIdError, type AcpSessionBridge, -} from './acpSessionBridge.js'; +} from './acp-session-bridge.js'; import type { BridgeEvent } from './event-bus.js'; import { mountWorkspaceMemoryRoutes } from './workspace-memory.js'; diff --git a/packages/cli/src/serve/workspace-memory.ts b/packages/cli/src/serve/workspace-memory.ts index 21058c79f6..5148b7ac32 100644 --- a/packages/cli/src/serve/workspace-memory.ts +++ b/packages/cli/src/serve/workspace-memory.ts @@ -16,7 +16,7 @@ import { } from '@qwen-code/qwen-code-core'; import { writeStderrLine } from '../utils/stdioHelpers.js'; import { isServeDebugMode } from './debug-mode.js'; -import type { AcpSessionBridge } from './acpSessionBridge.js'; +import type { AcpSessionBridge } from './acp-session-bridge.js'; import { createIdleWorkspaceMemoryStatus, STATUS_SCHEMA_VERSION, diff --git a/packages/cli/src/serve/workspace-service/__tests__/integration.test.ts b/packages/cli/src/serve/workspace-service/__tests__/integration.test.ts index c35daaadd7..e0a048e6b5 100644 --- a/packages/cli/src/serve/workspace-service/__tests__/integration.test.ts +++ b/packages/cli/src/serve/workspace-service/__tests__/integration.test.ts @@ -20,7 +20,7 @@ import type { DaemonWorkspaceService, WorkspaceRequestContext, } from '../types.js'; -import type { AcpSessionBridge } from '../../acpSessionBridge.js'; +import type { AcpSessionBridge } from '../../acp-session-bridge.js'; // --------------------------------------------------------------------------- // Test fixtures