Skip to Content
EntwicklerhandbuchDaemonKurzanleitung & Betrieb

Kurzanleitung & Betrieb

Diese Seite konzentriert sich darauf, wie man qwen serve startet, wie man überprüft, ob es funktioniert, und wie der interne Aufrufablauf von qwen serve bis zum lauschenden Server aussieht. Architektur, Komponenten und Details zum Wire-Protokoll befinden sich auf den anderen Seiten mit vertieften Einblicken in den Daemon.

1. Kürzester Weg

qwen serve

Ausgabe:

qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge, workspace=/your/cwd) qwen serve: bound to workspace "/your/cwd" qwen serve: bearer auth disabled (loopback default). Set QWEN_SERVER_TOKEN to enable.

Öffne http://127.0.0.1:4170/demo in einem Browser, um die Debug-Konsole zu sehen: Chat-UI, Ereignisstrom und Workspace-Inspektion. Im Standard-Loopback-Entwicklungsmodus wird /demo vor bearerAuth im Loopback-Route-Zweig von packages/cli/src/serve/server.ts registriert, sodass kein Token erforderlich ist.

2. Start-Rezepte

# 1. Local dev default (loopback, no token) qwen serve # 2. Explicit workspace + ephemeral port qwen serve --workspace /path/to/repo --port 0 # 3. Hardened loopback development (force bearer even on loopback) QWEN_SERVER_TOKEN=$(openssl rand -hex 32) qwen serve --require-auth # 4. Expose to LAN (non-loopback requires a token) QWEN_SERVER_TOKEN=$(openssl rand -hex 32) \ qwen serve --hostname 0.0.0.0 --port 4170 # 5. Tune for many sessions and a larger replay ring qwen serve --max-sessions 0 --event-ring-size 32000 # 6. Multi-client collaboration + strict MCP budget QWEN_SERVER_TOKEN=secret \ qwen serve --require-auth \ --mcp-client-budget 10 \ --mcp-budget-mode enforce # 7. Start with a consensus policy configured in settings.json # settings.json: { "policy": { "permissionStrategy": "consensus", "consensusQuorum": 2 } } qwen serve # 8. Debug logging QWEN_SERVE_DEBUG=1 qwen serve # 9. Disable the F2 pool (fallback to per-session MCP clients) QWEN_SERVE_NO_MCP_POOL=1 qwen serve # 10. Allow browser web UI cross-origin access QWEN_SERVER_TOKEN=secret \ qwen serve --allow-origin 'http://localhost:3000' # 11. Prompt deadline + SSE idle timeout qwen serve --prompt-deadline-ms 300000 --writer-idle-timeout-ms 600000 # 12. Keep the ACP child warm after the last session closes qwen serve --channel-idle-timeout-ms 60000 # 13. Enable HTTP rate limiting QWEN_SERVE_RATE_LIMIT=1 qwen serve

Mit dem gehärteten Loopback-Rezept (3) wird /demo nach bearerAuth registriert. Ein normaler Browserzugriff benötigt einen Auth-Header; verwende stattdessen curl oder ein SDK-Skript.

3. Alle Start-Flags

Die CLI ist in packages/cli/src/commands/serve.ts definiert:

FlagTypStandardErforderlich wennWirkung
--port <n>number4170-TCP-Port; 0 bedeutet vom Betriebssystem zugewiesener temporärer Port.
--hostname <host>string127.0.0.1Nicht-Loopback erfordert TokenBind-Adresse. Loopback-Werte: 127.0.0.1, localhost, ::1, [::1]. Eckige Klammern bei [::1] werden automatisch entfernt; Eingabe im Format host:port wird abgelehnt mit Hinweis auf --port.
--token <s>stringUmgebungsvariable / keinesNicht-Loopback und --require-authBearer-Token; wird einmal getrimmt. Erscheint in /proc/<pid>/cmdline, daher QWEN_SERVER_TOKEN bevorzugen. Boot stderr warnt ebenfalls davor.
--max-sessions <n>number20-Obergrenze für aktive Sitzungen. Überschreitung führt zu 503. 0 bedeutet unbegrenzt. NaN / negative Werte werfen einen Fehler.
--max-pending-prompts-per-session <n>number5-Obergrenze für akzeptierte, aber ausstehende/laufende Prompts pro Sitzung. Überschreitung führt zu 503. 0 / Infinity bedeutet unbegrenzt. Negative oder nicht-ganzzahlige Werte werfen einen Fehler.
--workspace <dir>stringprocess.cwd()-Gebundener Workspace. Muss ein absoluter Pfad sein, muss existieren und ein Verzeichnis sein. Boot kanonisiert ihn einmal via canonicalizeWorkspace. POST /session mit abweichendem cwd gibt 400 workspace_mismatch.
--max-connections <n>number256-Listener-Ebene server.maxConnections. 0 / Infinity bedeutet unbegrenzt. NaN / negative Werte verhindern Boot, um kein Fail-Open-Verhalten zu riskieren.
--require-authbooleanfalseToken erforderlichErweitert Bearer-Auth auf Loopback und /health. Boot verweigert den Start ohne Token.
--enable-session-shellbooleanfalseToken erforderlichErmöglicht direkte POST /session/:id/shell-Ausführung. Aufrufer müssen auch eine sitzungsgebundene X-Qwen-Client-Id senden.
--event-ring-size <n>number8000-Tiefe des SSE-Wiederholungsrings pro Sitzung. Weiches Limit ist MAX_EVENT_RING_SIZE = 1_000_000; außerhalb des gültigen Bereichs wird beim Brückenbau ein Fehler geworfen.
--http-bridgebooleantrue-Brückenmodus Stufe 1: ein qwen --acp-Kind, das vom Daemon gemultiplext wird. Stufe 2 (In-Prozess-Modus) ist noch nicht implementiert; --no-http-bridge fällt zurück und gibt eine Meldung auf stderr aus.
--mcp-client-budget <n>numberkeinesErforderlich für mcp-budget-mode=enforceObergrenze für Workspace-MCP-Clients. Muss eine positive ganze Zahl sein.
--mcp-budget-mode <m>'enforce' | 'warn' | 'off'warn wenn Budget gesetzt, sonst offenforce erfordert --mcp-client-budgetenforce lehnt ab, warn warnt nur bei 75%, off ist reine Beobachtung.
--allow-origin <pattern>wiederholbare Zeichenkettekeines-CORS-Allowlist, die die standardmäßige Origin-Ablehnung ersetzt. * erfordert ein Token.
--allow-private-auth-base-urlbooleanfalse-Erlaubt die Installation eines Auth-Provider-baseUrl auf localhost / privatem Netzwerk. Nur für vertrauenswürdige lokale Entwicklung verwenden.
--prompt-deadline-ms <n>numberkeines-Serverseitiges Prompt-Wallclock-Limit in ms; Timeout bricht den Prompt ab.
--writer-idle-timeout-ms <n>numberkeines-Inaktivitäts-Timeout pro SSE-Verbindung in ms.
--channel-idle-timeout-ms <n>number0-Hält das ACP-Kind nach dem Schließen der letzten Sitzung am Leben. 0 bedeutet sofortige Freigabe.
--session-reap-interval-ms <n>number60000-Intervall des Sitzungs-Reaper-Scans. 0 deaktiviert ihn.
--session-idle-timeout-ms <n>number1800000-Inaktivitäts-Timeout für getrennte Sitzungen. 0 deaktiviert es.
--rate-limit / --no-rate-limitbooleanUmgebungsvariable / aus-Aktiviert oder deaktiviert HTTP-Ratenbegrenzung pro Stufe.
--rate-limit-prompt <n>number10--rate-limitPrompt-Anfragen pro Fenster.
--rate-limit-mutation <n>number30--rate-limitMutationsanfragen pro Fenster.
--rate-limit-read <n>number120--rate-limitLeseanfragen pro Fenster.
--rate-limit-window-ms <n>number60000--rate-limitFensterlänge für Ratenbegrenzung; muss >= 1000 sein.

4. Umgebungsvariablen

EnvEntsprechendes Flag / Effekt
QWEN_SERVER_TOKENEntspricht --token; --token gewinnt. Wird beim Start einmal getrimmt, um einen nachgestellten Zeilenumbruch von cat token.txt zu vermeiden.
QWEN_SERVE_DEBUG1 / true / on / yes (Groß-/Kleinschreibung egal) aktiviert ausführliche stderr-Logs.
QWEN_SERVE_NO_MCP_POOL1 deaktiviert den Workspace-MCP-Pool vollständig und fällt auf den pro-Sitzung McpClientManager zurück. Fähigkeiten bewerben dann mcp_workspace_pool / mcp_pool_restart nicht mehr.
QWEN_SERVE_MCP_CLIENT_BUDGETACP-Child-internes Budget-Input. Das CLI erzeugt es aus --mcp-client-budget über childEnvOverrides; es ist kein Fallback auf die Umgebungsvariable des Elternprozesses.
QWEN_SERVE_MCP_BUDGET_MODEACP-Child-interner Budget-Modus. Das CLI erzeugt es aus --mcp-budget-mode über childEnvOverrides; es ist kein Fallback auf die Umgebungsvariable des Elternprozesses.
QWEN_SERVE_PROMPT_DEADLINE_MSUmgebungsvariablen-Fallback für --prompt-deadline-ms.
QWEN_SERVE_WRITER_IDLE_TIMEOUT_MSUmgebungsvariablen-Fallback für --writer-idle-timeout-ms.
QWEN_SERVE_MCP_POOL_TRANSPORTSVom ACP-Child gelesen. Kommagetrennte Whitelist gepoolter Transporte; Standard ist stdio,websocket.
QWEN_SERVE_MCP_POOL_DRAIN_MSVom ACP-Child gelesen. Verzögerung für das Leeren inaktiver Pool-Einträge; Standard ist 30000, begrenzt auf 1000..600000 ms.
QWEN_SERVE_RATE_LIMIT1 / true aktiviert Ratenbegrenzung; CLI-Flag gewinnt.
QWEN_SERVE_RATE_LIMIT_PROMPTUmgebungsvariablen-Fallback für --rate-limit-prompt.
QWEN_SERVE_RATE_LIMIT_MUTATIONUmgebungsvariablen-Fallback für --rate-limit-mutation.
QWEN_SERVE_RATE_LIMIT_READUmgebungsvariablen-Fallback für --rate-limit-read.
QWEN_SERVE_RATE_LIMIT_WINDOW_MSUmgebungsvariablen-Fallback für --rate-limit-window-ms.

Die Überschreibungen pro Handle sind beabsichtigt: Zwei Daemons, die im selben Prozess laufen, konkurrieren nicht um process.env. defaultSpawnChannelFactory erfasst die Umgebung zum Zeitpunkt des Spawnens.

5. settings.json wird ebenfalls gelesen

Boot ruft loadSettings(boundWorkspace) einmal auf:

SchlüsselTypVerhalten
policy.permissionStrategy'first-responder' | 'designated' | 'consensus' | 'local-only'Setzt BridgeOptions.permissionPolicy. Boot validiert mit validatePolicyConfig; unbekannte Werte werfen InvalidPolicyConfigError, anstatt stillschweigend zurückzufallen.
policy.consensusQuorumpositive GanzzahlN für die consensus-Richtlinie. Standard ist floor(M/2)+1. Wenn unter einer Nicht-consensus-Richtlinie gesetzt, wird es ignoriert und Boot protokolliert eine stderr-Warnung.
context.fileNameZeichenketteÜberschreibt getCurrentGeminiMdFilename() und steuert, welche Datei POST /workspace/init schreibt.
tools.disabledZeichenkette[]Wird durch normalizeDisabledToolList() normalisiert (trimmen, leere Einträge entfernen, deduplizieren), bevor der nächste ACP-Child-Spawn beeinflusst wird.
tools.approvalModeZeichenketteStandard-Genehmigungsmodus für Sitzungen.
telemetryObjektOTel-Konfiguration: enabled, otlpEndpoint, otlpProtocol, signal-spezifische Endpunkte und mehr. Siehe 17-configuration.md.
Fehler bei Einstellungs-I/O, z. B. fehlerhaftes JSON, führen zur Verwendung der Standardwerte. InvalidPolicyConfigError ist die Ausnahme: eine Fehlkonfiguration der Richtlinie führt zu einem expliziten Boot-Fehler.

6. Verweigerungsszenarien beim Boot (explizite Fehler)

run-qwen-serve.ts wirft absichtlich einen Fehler, anstatt auf Standardwerte zurückzufallen, in diesen Fällen:

SzenarioFehlerpräfix
Nicht-Loopback-Bindung ohne TokenRefusing to bind ... without a bearer token
--require-auth ohne TokenRefusing to start with --require-auth set but no bearer token
--workspace existiert nicht, ist kein Verzeichnis oder nicht absolutInvalid --workspace ...
--workspace stat-Berechtigung verweigertInvalid --workspace ...: permission denied
--mcp-client-budget ist keine positive GanzzahlMust be a positive integer
--mcp-budget-mode=enforce ohne Budgetrequires a positive mcpClientBudget
--hostname ist als localhost:4170 geschriebenlooks like a "host:port" combination. Use --port
--hostname [::1]:8080Invalid --hostname ... brackets indicate an IPv6 literal but the value is not a clean [addr] form
--max-connections ist NaN oder negativMust be >= 0
--event-ring-size > 1_000_000Wird während der Bridge-Konstruktion geworfen
--allow-origin '*' ohne TokenRefusing to start with --allow-origin '*' but no bearer token configured
--prompt-deadline-ms / --writer-idle-timeout-ms ist keine positive GanzzahlMust be a positive integer
Unbekanntes policy.permissionStrategy oder nicht-positives policy.consensusQuorumInvalidPolicyConfigError

7. Überprüfungs-Checkliste mit curl

# 1. Liveness curl http://127.0.0.1:4170/health # -> {"status":"ok"} # 1.1 Deep Health curl -s 'http://127.0.0.1:4170/health?deep=1' | jq # 2. Fähigkeiten curl -s http://127.0.0.1:4170/capabilities | jq # 3. Preflight-Bereitschaft curl -s http://127.0.0.1:4170/workspace/preflight | jq # 4. Umgebungs-Snapshot (Geheimnisse zeigen nur ihre Existenz an) curl -s http://127.0.0.1:4170/workspace/env | jq # 5. MCP-Pool / Budget-Snapshot curl -s http://127.0.0.1:4170/workspace/mcp | jq # 6. Sitzung erstellen curl -s -X POST http://127.0.0.1:4170/session \ -H 'Content-Type: application/json' \ -H 'X-Qwen-Client-Id: curl-debug' \ -d '{}' | jq # 7. SSE-Ereignisse abonnieren (ersetze <sid>) curl -N \ -H 'Accept: text/event-stream' \ -H 'X-Qwen-Client-Id: curl-debug' \ -H 'Last-Event-ID: 0' \ 'http://127.0.0.1:4170/session/<sid>/events' # 8. Demo-Seite open http://127.0.0.1:4170/demo

Wenn die Bearer-Authentifizierung aktiviert ist, füge -H "Authorization: Bearer $QWEN_SERVER_TOKEN" zu jeder Anfrage hinzu.

8. Kann die Demo-Seite verwendet werden?

Ja. Sie wird von getDemoHtml(port) in packages/cli/src/serve/demo.ts als eigenständiges HTML ohne externe Abhängigkeiten implementiert.

StartmodusWo /demo registriert istDirekte Browser-Navigation
Loopback ohne --require-authserver.ts Loopback-Pre-Auth-Route-Zweig, vor bearerAuthFunktioniert ohne Token
Loopback mit --require-authserver.ts Post-Auth-Route-Zweig, nach bearerAuthSchwierig von einem normalen Browser aus zu verwenden; curl oder SDK nutzen
Nicht-Loopback-Bindungserver.ts Post-Auth-Route-Zweig, nach bearerAuthGleiches wie oben
CSP ist default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none', plus X-Frame-Options: DENY. Die Seite kann nur 'self' (den Daemon) abrufen und keine externen Skripte oder Styles laden.

9. Aufrufkette von qwen serve bis zum lauschenden Server

qwen serve | v (process) packages/cli/index.ts main() | v gemini.tsx main() - parseArguments() | v (yargs assembly) config/config.ts import { serveCommand } ... config/config.ts .command(serveCommand) config/config.ts await yargsInstance.parse() | v (handler) commands/serve.ts handler(argv) - boot pre-checks commands/serve.ts const { runQwenServe } = await import('../serve/index.js') # lazy load commands/serve.ts await runQwenServe({...}) | v serve/run-qwen-serve.ts runQwenServe(opts, deps) | |- trim token | |- hostname mismatch fallback | |- auth preflight | |- workspace validation + canonicalization | |- MCP budget validation + childEnvOverrides | |- loadSettings + validatePolicyConfig | |- PermissionAuditRing + publisher | |- resolveBridgeFsFactory | `- createHttpAcpBridge({...}) | v serve/run-qwen-serve.ts const app = createServeApp(opts, () => actualPort, {...}) | v serve/server.ts createServeApp() - builds Express app (**does not listen**) | |- middleware chain (Host allowlist / CORS / bearerAuth / mutation gate / rate limit) | |- route mounting (health / demo / capabilities / workspace / session / SSE / ACP HTTP) | `- return app | v serve/run-qwen-serve.ts server = app.listen(port, hostname, cb) | |- server.maxConnections = cap | |- actualPort = server.address().port | |- write "qwen serve listening on ..." | |- register SIGINT / SIGTERM (onSignal) | `- resolve(handle: RunHandle) | v commands/serve.ts await blockForever() // block forever until signal

Wichtige Fakten:

  • createServeApp baut nur; es lauscht nicht. Es gibt eine express()-Instanz mit Middleware und Routen zurück. Der Aufrufer besitzt app.listen(). server.test.ts verwendet die Factory auf diese Weise in etwa 25 Tests, daher vermeidet die Factory absichtlich die Lebenszyklusverwaltung.
  • () => actualPort ist ein Lazy-Closure. actualPort wird im app.listen-Callback zugewiesen. Die hostAllowlist-Middleware liest ihn bei Bedarf aus, sodass auch kurzlebige Ports (--port 0) den Host-Header korrekt prüfen.
  • await blockForever() ist beabsichtigt. Wenn yargs.parse() auflöst, fällt die oberste CLI-Ebene in den interaktiven TUI-Einstiegspunkt (gemini.tsx). SIGINT / SIGTERM beenden über den onSignal-Pfad von runQwenServe.

10. Aufteilung der HTTP-Routendateien

Die Hauptassemblierung erfolgt in createServeApp() in server.ts, das vier modulare Routendateien einbindet:

RoutenDateiEinhängepunkt
/health, /demo, /capabilities, alle Session-Routen, Device-Flow, Permission-Vote, SSE und Single-Server-MCP-Neustartpackages/cli/src/serve/server.tsDirekt in createServeApp() registriert
/workspace/memory (GET/POST)packages/cli/src/serve/workspace-memory.tsmountWorkspaceMemoryRoutes()
Alle CRUD-Routen für /workspace/agentspackages/cli/src/serve/workspace-agents.tsmountWorkspaceAgentsRoutes()
GET /file, /file/bytes, /list, /glob, /statpackages/cli/src/serve/routes/workspace-file-read.tsregisterWorkspaceFileReadRoutes()
POST /file/write, /file/editpackages/cli/src/serve/routes/workspace-file-write.tsregisterWorkspaceFileWriteRoutes()

Die vollständige Referenz zu den Routen und zum Drahtprotokoll findest du in ../qwen-serve-protocol.md. Zur Architektur siehe 01-architecture.md.

11. Sanftes vs. hartes Herunterfahren

  • Erstes SIGINT / SIGTERM -> runQwenServe onSignal -> zweiphasiges Graceful-Shutdown:
    1. bridge.shutdown(): jeder Kanal erhält KILL_HARD_DEADLINE_MS (10s), dann channel.kill().
    2. server.close(): laufende Requests werden abgearbeitet, SHUTDOWN_FORCE_CLOSE_MS (5s) löst closeAllConnections() aus, danach gilt eine zweite 2s-Frist.
  • Zweites SIGINT / SIGTERM während des Herunterfahrens -> bridge.killAllSync() sendet synchron SIGKILL an alle ACP-Kinder und ruft process.exit(1) auf, um verwaiste Prozesse zu vermeiden. RunHandle.close() von runQwenServe zurückgegeben ist das programmatische Äquivalent für Einbettungen und Tests.

12. Eingebetteter Aufruf (CLI umgehen)

import { runQwenServe } from '@qwen-code/qwen-code/serve'; const handle = await runQwenServe({ port: 0, // ephemeral hostname: '127.0.0.1', mode: 'http-bridge', maxSessions: 20, workspace: '/abs/path/to/repo', }); console.log(`Daemon at ${handle.url}`); // ... call handle.bridge directly or access handle.server await handle.close(); // programmatic shutdown

Oder die Express-App direkt abrufen und selbst lauschen:

import { createServeApp } from '@qwen-code/qwen-code/serve'; const app = createServeApp( { port: 0, hostname: '127.0.0.1', mode: 'http-bridge', maxSessions: 20, }, () => 0, { /* deps: bridge, fsFactory, ... */ }, ); const server = app.listen(0, '127.0.0.1', () => { console.log('listening on', server.address()); });

Hinweis: Beim direkten Aufruf von createServeApp ist der Standardwert fsFactory.trusted = false. Das serverseitige ACP writeTextFile wird als untrusted_workspace abgelehnt, und eine Stderr-Warnung wird einmal ausgegeben. Entweder deps.fsFactory mit explizitem Vertrauen injizieren, deps.bridge injizieren oder das standardmäßige vertrauensgesteuerte Verhalten akzeptieren.

13. Debugging-Rezepte

Siehe den Abschnitt zum Debuggen in 19-observability.md. Die üblichen Befehle sind:

# Is the daemon alive? curl http://127.0.0.1:4170/health # Which capabilities are advertised? curl -s http://127.0.0.1:4170/capabilities | jq # Daemon-host readiness curl -s http://127.0.0.1:4170/workspace/preflight | jq # Tail live SSE curl -N -H 'Accept: text/event-stream' \ -H 'Last-Event-ID: 0' \ 'http://127.0.0.1:4170/session/<sid>/events' # Verbose logs QWEN_SERVE_DEBUG=1 qwen serve

Referenzen

  • CLI-Einstieg: packages/cli/src/commands/serve.ts
  • Bootstrap: packages/cli/src/serve/run-qwen-serve.ts
  • Express-Factory: packages/cli/src/serve/server.ts
  • Middleware: packages/cli/src/serve/auth.ts
  • Bridge-Factory: packages/acp-bridge/src/bridge.ts
  • HTML der Demo-Seite: packages/cli/src/serve/demo.ts
  • Benutzerdokumentation: ../../users/qwen-serve.md
  • Drahtprotokoll: ../qwen-serve-protocol.md
Last updated on