Skip to Content
Руководство для разработчиковРежим демона (углубленное руководство для разработчиков)Среда выполнения Serve

Среда выполнения Serve

Обзор

packages/cli/src/serve/ — это загрузочный слой для qwen serve. Он преобразует флаги CLI в ServeOptions, проверяет конфигурацию запуска, собирает приложение Express, подключает middleware, регистрирует маршруты, предоставляет провайдеры preflight/статуса для хоста демона, поддерживает кольцо аудита разрешений и управляет двухфазным процессом корректного завершения работы (graceful shutdown). Работа с HTTP находится в этом слое; работа с ACP находится на уровень ниже в @qwen-code/acp-bridge (см. 03-acp-bridge.md).

Обязанности

  • Парсинг и валидация ServeOptions: адрес прослушивания, аутентификация, рабочее пространство (workspace), лимиты сессий/подключений, бюджет/пул MCP, CORS, таймауты простоя для промптов/SSE/сессий, rate limit и связанные переключатели.
  • Канонизация основного рабочего пространства ровно один раз, а также канонизация каждого повторного --workspace перед регистрацией сессионных сред выполнения. Основная каноническая форма используется в /capabilities.workspaceCwd, fallback для POST /session и в основном bridge.
  • Разрешение bearer: --token, затем QWEN_SERVER_TOKEN, затем — если ни один источник не задан и запрошенный --hostname не является loopback (буквальный localhost разрешается первым) — генерируется эфемерный 128-битный base64url bearer (22 символа), выводимый один раз при запуске. Loopback-записи никогда не генерируют токен и сохраняют доверенный режим без токена, если не установлен --require-auth. Генерация зависит от записи, а отказ при загрузке читает разрешённый адрес, что даёт два исключения: localhost, разрешающийся вне loopback, никогда не генерирует токен и загружается только при наличии разрешённого источника токена (иначе Refusing to bind …); а не-буквальное имя, разрешающееся в loopback, генерирует токен, теряя доверенный режим без токена, поэтому его bearer выводит только токен.
  • Отклонение небезопасных или невалидных конфигураций запуска: привязка не к loopback-интерфейсу без токена, --require-auth без токена, wildcard или не-loopback HTTP(S) --allow-origin без токена, mcpBudgetMode='enforce' без положительного mcpClientBudget, несуществующий или не являющийся директорией --workspace, а также невалидные значения таймаутов или rate-limit.
  • Создание фабрики WorkspaceFileSystem, издателя аудита разрешений (permission audit publisher), DaemonStatusProvider и acp-bridge.
  • Сборка приложения Express, подключение middleware (удаление loopback Origin -> access log -> захват входящего trace-id -> hostAllowlist -> удаление remote same-origin Origin -> allowOriginCors поверх мутабельного allowlist origin -> pre-auth /health -> pre-auth ресурсы Web Shell -> вебхуки каналов -> bearerAuth -> rate limit -> JSON parser -> telemetry -> per-route mutationGate) и монтирование маршрутов для сессий, CRUD-операций с рабочим пространством, файлами, аутентификации через device-flow, голосования за разрешения и ACP HTTP. (Безусловная стена denyBrowserOriginCors сохраняется только в бутстрап-приложении run-qwen-serve.ts.)
  • Привязка порта прослушивания и регистрация обработчиков сигналов.
  • Запуск двухфазного завершения работы по SIGINT/SIGTERM; принудительный выход при получении второго сигнала.

Архитектура

Точка входа: runQwenServe(opts, deps) в packages/cli/src/serve/run-qwen-serve.ts. Возвращает RunHandle ({ url, port, close, ... }).

Фабрика приложения: createServeApp(opts, getPort, deps) в packages/cli/src/serve/server.ts. Собирает Express Application. Прямые встраивающие модули и тесты вызывают её без обёртки бутстрапа.

Реестр возможностей: SERVE_CAPABILITY_REGISTRY в packages/cli/src/serve/capabilities.ts. Каждый тег имеет версию since и опциональные modes. Условные теги исключаются, если их деплойментный или рантаймный предикат ложен; реестр и карта предикатов являются источником истины. См. 11-capabilities-versioning.md.

Middleware (packages/cli/src/serve/auth.ts, server.ts, server/self-origin.ts и server/access-log.ts):

Middleware, в порядке регистрацииНазначениеПримечания
installSelfOriginStripMiddlewareУдаляет заголовок Origin, совпадающий с loopback same-origin значением для привязанного порта, чтобы собственные POST/fetch-вызовы loopback Web Shell никогда не считались cross-origin.Первый middleware в runtime-приложении. Сопоставляет обе схемы и привязанный loopback-хост, опускает порты по умолчанию для схемы согласно RFC 7230 §5.4.
Access-log middlewareЗаписывает метод, путь, статус, durationMs, sessionId и clientId в DaemonLogger при завершении запроса.Регистрируется перед каждым гейтом, поэтому 401 / 403 / 429 короткие замыкания также логируются. Исключён по точному пути: GET /health и POST */heartbeat, чтобы эти зонды жизнеспособности никогда не логировались — включая случаи, когда гейт ниже их отклоняет (HEAD /health и GET /health/ логируются как обычный запрос). Успешные потоки GET */events также исключаются. Ограничен burst-лимитом 60 строк с пополнением 2/с; переполнение сворачивается в предупреждение access logs suppressed. Отказы pre-authentication гейта (Host allowlist, CORS-стена, проверка same-origin credential для remote) используют отдельный бюджет 30/1 с, чтобы credential-less флуд этих отказов не мог лишить оператора собственных строк за этим предупреждением; bearerAuth 401 (флуд без Origin) не помечаются и по-прежнему расходуют бюджет оператора — без изменений с момента изменения порядка.
Захват входящего trace-idЗахватывает trace id traceparent вызывающей стороны до того, как любой гейт сможет выполнить короткое замыкание.Позволяет access log связать строку 401 / 429 / 400 / 404 с трассировкой вызывающей стороны в деплойментах с выключенной телеметрией, где это единственная такая связь.
hostAllowlist(bind, getPort)На loopback проверяет, что Host принадлежит localhost, 127.0.0.1, [::1], host.docker.internal или точному привязанному loopback-адресу, а также фактическому порту; формы без порта принимаются на портах 80 и 443.Защита от DNS rebinding; поэтому также покрывает pre-auth маршруты /health ниже. Сравнение без учёта регистра, кэшируется для каждого порта. Намеренно не работает на не-loopback привязках, где bearer является слоем аутентификации. LAN-листенер Local Control всегда применяет проверку Host по рекламируемому authority, независимо от основной привязки.
installRemoteSelfOriginMiddlewareНа не-loopback основном слушателе с токеном выполняет bearer-аутентификацию запроса, чей Origin равен схеме сокета плюс нормализованный Host, затем удаляет этот Origin.Это позволяет same-origin HTTP-мутациям встроенного Web Shell проходить без --allow-origin. Не работает на loopback привязках и когда токен не настроен. Pre-auth маршруты Web Shell (/, //, /assets*, /mcp-app-sandbox, точные навигации /session/:id) пропускают проверку credential. Forwarded-заголовки никогда не используются.
allowOriginCorsВсегда устанавливается на runtime-приложение поверх MutableOriginAllowlist: записи --allow-origin <pattern> засевают его, Local Control добавляет LAN origin, пока включён; несовпадающие origin получают 403 deny envelope.См. 12-auth-security.md. Его отказы логируются access log выше, кроме исключений health/heartbeat.
Pre-auth /healthМаршрут жизнеспособности, регистрируемый перед bearerAuth на обычной loopback привязке.Отключается при --require-auth и никогда не регистрируется pre-auth на не-loopback привязке; в этих случаях /health регистрируется после bearerAuth. Local Control слушатель аутентифицирует собственный /health даже в pre-auth позиции.
Статические ресурсы Web Shell и песочница MCP App/, /assets*, /mcp-app-sandbox и точные навигации /session/:id, монтируемые перед bearerAuth.Браузер не может добавить Authorization к навигации или подресурсу <script src>, а статическая оболочка не содержит секретов. Fallback SPA deep-link регистрируется после всех API-маршрутов. --no-web отключает.
Маршруты вебхуков каналовPOST /channels/:channelName/webhooks/:source, регистрируется перед bearerAuth.Аутентифицируется собственным x-qwen-webhook-secret; ротация bearer токена демона не ротирует секреты вебхуков.
bearerAuth(token)SHA-256 плюс timingSafeEqual — константное сравнение bearer.Открытый пропуск, когда токен не настроен (loopback dev по умолчанию). Схема Bearer без учёта регистра.
Rate-limit middlewareОпциональный token bucket для каждого уровня для маршрутов промптов, мутаций и чтения.Регистрируется после bearerAuth и до парсинга JSON; возвращает 429 до парсинга, когда bucket исчерпан.
express.json({ limit: '10mb' })Парсинг тела JSON.Ошибки парсинга возвращают 400.
daemonTelemetryMiddlewareОборачивает классифицированные запросы к daemon API, достигшие этой точки, в OpenTelemetry span через withDaemonRequestSpan.Атрибуты включают канонический маршрут, хеш разрешённого рабочего пространства, sessionId, clientId и код статуса. Более ранние отказы auth, rate-limit и body-parser находятся за пределами этой span-границы.
createMutationGate (per-route)Гейт на уровне маршрута (opt-in) для мутаций, требующих полномочий оператора. Допускаются запросы от доверенного основного слушателя, запросы с bearer-аутентификацией и сопряжённые запросы Local Control.Запрос от основного слушателя без токена, достигший строгого гейта без доверенного loopback-authority, возвращает 401 { code: 'token_required' }. Отсутствующие или невалидные настроенные учётные данные отклоняются ранее bearer middleware с обычным 401 Unauthorized. Не глобальный app.use; маршруты вызывают mutate({ strict: true }) по мере необходимости.

Подсистемы:

ПутьРоль
serve/fs/Фабрика WorkspaceFileSystem плюс policy.ts (проверки размера/доверия/бинарности), paths.ts (канонизация, resolveWithin, отклонение symlink), audit.ts и типизированные значения FsError.
serve/routes/workspace-file-read.ts, workspace-file-write.tsHTTP-обработчики для GET /file, GET /file/bytes, POST /file/write и POST /file/edit.
serve/workspace-memory.tsGET/POST /workspace/memory (CRUD QWEN.md).
serve/workspace-agents.tsGET/POST/DELETE /workspace/agents (CRUD субагентов).
serve/daemon-status-provider.tsСнимок окружения и preflight-ячейки хоста демона: версия Node, точка входа CLI, статистика рабочего пространства, ripgrep, git, npm.
serve/permission-audit.tsPermissionAuditRing (FIFO на 512 записей) и createPermissionAuditPublisher.
serve/auth/device-flow.ts, qwen-device-flow-provider.tsМаршруты OAuth device-flow. См. 12-auth-security.md.
serve/daemon-logger.tsСтруктурированные файловые логи DaemonLogger. См. 19-observability.md.
serve/debug-mode.tsОбщий предикат isServeDebugMode(), управляющий подробным контекстом ошибок в HTTP-ответах.
serve/acp-http/Транспорт ACP Streamable HTTP (RFD #721), монтируется в /acp. Семь файлов реализуют JSON-RPC POST, SSE GET, DELETE teardown и общее использование bridge параллельно с REST-поверхностью.
serve/web-shell-static.ts, serve/web-shell-resolver.tsПоиск и монтирование собранных ресурсов Web Shell (браузерный UI демона) в /, /assets и /session/:id, а также fallback SPA deep-link, регистрируемый после всех API-маршрутов. Монтируется до bearerAuth в любом режиме запуска, поскольку браузер не может добавить Authorization к навигации или подресурсу. API-вызовы следуют обычной политике authority: настроенные токены контролируют обычные API-маршруты, кроме loopback /health, если не установлен --require-auth, а входные вебхуки каналов всегда используют свой собственный общий секрет, а основной доверенный loopback-слушатель без токена имеет полный доступ оператора. Деградирует до API-only, если ресурсы отсутствуют; --no-web отключает.

Импорты из пакета ACP bridge:

  • Примитивы event-bus импортируются из @qwen-code/acp-bridge/eventBus.
  • Примитивы статуса импортируются из @qwen-code/acp-bridge/status.
  • serve/acp-session-bridge.ts остаётся как локальный для CLI фасад совместимости для более широкой поверхности bridge.

Поток выполнения

Последовательность загрузки

До того как runQwenServe() запускает эту последовательность, режим CLI --open-with-auth проверяет возможность работы с loopback/Web Shell и заполняет ServeOptions.token выбранным настроенным токеном или 32 случайными байтами (256-битный bearer) в кодировке base64url, если выбор пуст. Это сгенерированное значение является обычным настроенным токеном для всех шагов ниже — именно поэтому --require-auth --open-with-auth загружается — и это отдельный генератор от эфемерного bearer для не-loopback в шаге 1. Прямые встраивающие модули, вызывающие createServeApp самостоятельно, никогда не генерируют токен.

  1. Разрешение токена из opts.token или QWEN_SERVER_TOKEN с обрезкой, чтобы завершающий символ новой строки от cat token.txt не мог скрыто нарушить сравнение bearer. Когда запрошенный --hostname не является loopback (буквальный localhost разрешается первым) и ни один источник не присутствует, генерируется эфемерный 128-битный (16-байтный) bearer как 22 символа base64url вместо отказа; он выводится один раз удалённым быстрым стартом после listen() и ротируется при каждом перезапуске. Loopback-записи никогда не генерируют, поэтому сохраняют доверенный режим без токена. Явно пустой источник (--token '' или QWEN_SERVER_TOKEN установленный в пустое или только пробельное значение) не считается «отсутствующим», поэтому всегда подавляет генерацию — но пустота определяет разрешённый токен только в одном направлении: пустой --token затеняет установленное значение env и разрешается в отсутствие токена, тогда как пустое env разрешается в отсутствие токена только когда --token не передан (непустой --token всё равно побеждает); в обоих случаях не-loopback привязка без разрешённого токена всё равно не проходит проверки ниже.
  2. Защита от опечаток в hostname: --hostname localhost:4170 вызывает ошибку и предлагает использовать --port.
  3. Preflight аутентификации: не-loopback привязка без разрешённого токена отклоняется — это достижимо через явно пустой источник или через привязку localhost, чьё одноразовое разрешение попадает вне loopback (генерация зависит от записи, поэтому там ничего не было сгенерировано); --require-auth отклоняет привязку без токена, что после шага 1 означает привязку к loopback без настроенного источника. Защитные механизмы wildcard и не-loopback HTTP(S) --allow-origin читают тот же разрешённый токен, поэтому на не-loopback привязке сгенерированный bearer удовлетворяет им, и эти отказы тоже только для loopback.
  4. Валидация рабочего пространства: абсолютный путь, существует, является директорией. EACCES / EPERM оборачиваются, чтобы указать на флаг.
  5. Канонизация рабочего пространства: canonicalizeWorkspace(rawWorkspace) один раз запускает realpathSync.native и передаёт результат в /capabilities, fallback для POST /session и в bridge.
  6. Валидация бюджета MCP: положительное целое число; enforce требует указания бюджета.
  7. Вывод переключателя пула MCP: родительская переменная окружения QWEN_SERVE_NO_MCP_POOL=1 устанавливает mcpPoolActive=false, поэтому возможности честно исключают mcp_workspace_pool и mcp_pool_restart.
  8. Валидация CORS / таймаутов / rate-limit: wildcard и не-loopback HTTP(S) значения --allow-origin требуют разрешённый токен (см. шаг 3, почему эти отказы только для loopback); значения таймаутов простоя промптов, writer, канала, сессии, reaper и окна rate-limit приводят к быстрому сбою при невалидности.
  9. childEnvOverrides для каждого handle: передача QWEN_SERVE_MCP_CLIENT_BUDGET и QWEN_SERVE_MCP_BUDGET_MODE дочернему процессу ACP через BridgeOptions.childEnvOverrides вместо мутации process.env.
  10. Однократная загрузка settings.json: чтение context.fileName, policy.permissionStrategy и policy.consensusQuorum. Повреждённые файлы откатываются к значениям по умолчанию. validatePolicyConfig() проверяет policy.* на соответствие SERVE_CAPABILITY_REGISTRY.permission_mediation.modes; неизвестные стратегии или неположительный consensusQuorum выбрасывают InvalidPolicyConfigError. Кворум, установленный для стратегии, отличной от consensus, логирует предупреждение в stderr.
  11. Выделение PermissionAuditRing (512 записей).
  12. Сборка fsFactory: runQwenServe по умолчанию использует trusted: true; прямые вызовы createServeApp по умолчанию используют trusted: false и выводят одно предупреждение.
  13. createHttpAcpBridge, см. 03-acp-bridge.md.
  14. createServeApp собирает Express.
  15. Создание и привязка HTTP(S)-сервера к жизненному циклу перед прослушиванием, затем вызов server.listen(port, hostname) и разрешение фактического getPort() для allowlist хоста. Владение Conversations не может начаться, пока этот слушатель и оставшиеся хост-гейты запуска не будут готовы.
  16. Регистрация обработчиков SIGINT / SIGTERM для корректного завершения работы через общий жизненный цикл приложения.

Корректное завершение работы

  1. Закрытие допуска и начало всех дренирований по первому сигналу:
    • Удаление реестра device-flow и отмена ожидающих потоков.
    • bridge.shutdown() помечает каждый канал isDying = true, отправляет корректное закрытие в stdin каждого дочернего процесса ACP, ждёт KILL_HARD_DEADLINE_MS (10 с) для каждого канала, затем при необходимости вызывает channel.kill().
  2. Закрытие слушателя, пока выполняются дренирования приложения и хоста:
    • server.close() прекращает приём новых соединений и позволяет завершиться выполняющимся запросам.
    • SHUTDOWN_FORCE_CLOSE_MS (5 с) инициирует server.closeAllConnections().
    • Второй дедлайн в 2 с при необходимости снова эскалирует завершение.
  3. Освобождение владения Conversations только после положительного доказательства завершения от слушателя, локальной работы приложения, работы хоста, очистки Live discovery и дренирований среды выполнения. Любое неполное доказательство отклоняет завершение вместо небезопасной передачи.
  4. Второй сигнал во время выхода:
    • bridge.killAllSync() + process.exit(1), чтобы избежать блокировки выхода демона осиротевшими дочерними процессами.

Состояние и жизненный цикл

RunHandle предоставляет:

  • url: разрешённый URL прослушивания, после разрешения эфемерного порта.
  • port: фактический порт, включая разрешение 0.
  • close(): программное завершение работы для встраивающих модулей и тестов.

Прямой вызов createServeApp возвращает только Application. Встраивающему модулю, которому нужны Live/Conversations, необходимо создать фактический Node-сервер, вызвать getServeAppLifecycle(app).bindServer(server) перед первым listen() и ожидать lifecycle.close() во время завершения работы. Без привязки обычные маршруты остаются доступными, но Live/Conversations завершаются с ошибкой (fail closed). Вызов обычного server.close() запускает очистку на основе событий, но встраивающий модуль всё равно должен ожидать lifecycle.close(), чтобы получить информацию о сбоях дренирования или освобождения владения.

Зависимости

Используется serve/ (upstream)Использует serve/ (downstream)
@qwen-code/acp-bridge: bridge, event bus, типы statusОбработчик подкоманды serve CLI qwen
packages/core: getAllMemoryFilenames, Config, WorkspaceContextПрямые встраивающие модули, тесты
ACP SDK (@agentclientprotocol/sdk): PROTOCOL_VERSION, ClientSideConnection через bridge
Express + body-parser, node:crypto, node:fs, node:path

Конфигурация

ИсточникКлючЭффект
EnvQWEN_SERVER_TOKENBearer-токен после обрезки.
EnvQWEN_SERVE_NO_MCP_POOL=1Принудительно устанавливает mcpPoolActive=false.
Env дочернего процесса ACPQWEN_SERVE_MCP_CLIENT_BUDGET / QWEN_SERVE_MCP_BUDGET_MODEГенерируется из --mcp-client-budget / --mcp-budget-mode и передаётся через childEnvOverrides.
EnvQWEN_SERVE_PROMPT_DEADLINE_MS / QWEN_SERVE_WRITER_IDLE_TIMEOUT_MSТаймауты простоя промпта / SSE по умолчанию.
EnvQWEN_SERVE_RATE_LIMIT*Переключатель rate-limit, лимиты промптов/мутаций/чтения и окно по умолчанию.
EnvQWEN_SERVE_DEBUG=1Подробные логи stderr. См. 19-observability.md.
Флаги--hostname, --portПривязка прослушивания.
Флаги--token, --require-auth, --enable-session-shellBearer-токен, усиление аутентификации на loopback и явный переключатель выполнения shell.
CLI-флаги--open-with-authЗапуск loopback Web Shell по умолчанию в выключенном состоянии, повторно использующий или генерирующий bearer-токен на время жизни процесса до запуска.
Флаг--workspaceПереопределяет process.cwd(); повторите для регистрации дополнительных изолированных сред выполнения рабочего пространства.
Флаги--max-sessions, --max-pending-prompts-per-session, --max-connections, --event-ring-sizeЛимиты Bridge / Express.
Флаги--mcp-client-budget=N, --mcp-budget-mode={off,warn,enforce}Передаётся дочернему процессу ACP.
Флаги--allow-origin, --allow-private-auth-base-urlAllowlist CORS для браузера и переключатель установки провайдера аутентификации localhost/private.
Флаг--web / --no-webОбслуживание или пропуск Web Shell UI в корне демона (по умолчанию обслуживается). --no-web оставляет демон в режиме API-only.
Флаги--prompt-deadline-ms, --writer-idle-timeout-ms, --channel-idle-timeout-ms, --initialize-timeout-msУправление таймаутом промпта, SSE writer, жизненным циклом простоя дочернего процесса ACP и таймаутом запросов дочернего процесса ACP.
Флаги--session-reap-interval-ms, --session-idle-timeout-msУправление очисткой (reaping) отключённых сессий.
Флаги--rate-limit*HTTP rate limit для каждого уровня.
settings.jsonpolicy.permissionStrategy, policy.consensusQuorumПолитика и кворум MultiClientPermissionMediator.
settings.jsoncontext.fileNameИмя файла памяти рабочего пространства, передаваемое в /workspace/init через contextFilename сервиса рабочего пространства.

См. 17-configuration.md для сводной документации.

Важные замечания и известные ограничения

  • Прямой вызов createServeApp без deps.fsFactory или deps.bridge по умолчанию устанавливает trusted: false; ACP writeTextFile на стороне агента отклоняет запрос с ошибкой untrusted_workspace. Предупреждение выводится один раз.
  • Runtime-приложение запускает allowOriginCors поверх мутабельного allowlist; несовпадающие значения Origin получают 403 deny envelope (безусловная стена denyBrowserOriginCors сохраняется только в бутстрап-приложении). Loopback Web Shell работает, потому что другой middleware сначала удаляет совпадающие значения same-origin для loopback; на не-loopback привязке с токеном same-origin XHR оболочки bearer-аутентифицированы и их Origin удаляется перед стеной, поэтому им не нужен --allow-origin. Три случая всё ещё требуют записи в allowlist: WebSocket-обновления (terminal, voice), TLS-терминирующий фронт-прокси, чей https origin никогда не совпадает с открытым сокетом, и любой plain-HTTP посредник, переписывающий заголовок Host — nginx по умолчанию с proxy_set_header Host $proxy_host и k8s Ingress оба делают это. Только трансляция порта не требует ничего на не-loopback привязке (docker -p 8080:4170): проверка сравнивает Origin только с нормализованным пересланным Host и никогда не обращается к порту прослушивания (только порты по умолчанию для схемы :80/:443 удаляются; порт не по умолчанию должен сохраняться в нём дословно). На привязке loopback по умолчанию это не так: Host allowlist от DNS-rebinding принимает только собственный порт демона, поэтому туннель с трансляцией порта (ssh -L 8080:localhost:4170) отклоняется с 403 Invalid Host header для каждого запроса, включая документ оболочки, и --allow-origin не может это переопределить — перенаправляйте тот же порт или привязывайтесь к не-loopback. Решение для случаев WebSocket и TLS-терминирующего прокси — --allow-origin <origin>; посредник с переписыванием Host может быть настроен на пересылку Host дословно — что не может помочь, когда TLS терминируется на прокси, потому что схема читается из собственного сокета демона.
  • Порядок body-parser: маршруты, использующие mutate({ strict: true }), возвращают 401 только после express.json(). В худшем случае это --max-connections × express.json({limit: '10mb'}), что может привести к выделению до 2,5 ГБ временной памяти при максимальной нагрузке на loopback-листенер; этот компромисс является осознанным.
  • Несколько демонов в одном процессе должны использовать childEnvOverrides для каждого handle; мутация process.env приводит к гонкам, поскольку defaultSpawnChannelFactory делает снимок окружения в момент spawn.

Ссылки

  • packages/cli/src/serve/run-qwen-serve.ts (бутстрап, валидация загрузки, корректное завершение работы)
  • packages/cli/src/serve/server.ts (createServeApp(), сборка middleware и маршрутов)
  • packages/cli/src/serve/auth.ts (CORS, allowlist Host, bearer-аутентификация, mutation gate)
  • packages/cli/src/serve/rate-limit.ts (HTTP rate limit для каждого уровня)
  • packages/cli/src/serve/capabilities.ts (реестр возможностей и условное объявление)
  • packages/cli/src/serve/types.ts (ServeOptions, CapabilitiesEnvelope)
  • packages/cli/src/serve/daemon-status-provider.ts
  • packages/cli/src/serve/permission-audit.ts
  • Issues: #3803 , #4175 
Last updated on