diff --git a/apps/kimi-code/src/cli/sub/daemon.ts b/apps/kimi-code/src/cli/sub/daemon.ts index 8c65dc88d..60dfbfd04 100644 --- a/apps/kimi-code/src/cli/sub/daemon.ts +++ b/apps/kimi-code/src/cli/sub/daemon.ts @@ -66,6 +66,8 @@ export type EnsureDaemonResult = logPath: string; }; +export type DaemonStartupReporter = (message: string) => void; + export interface EnsureDaemonRunningDeps { isDaemonHealthy(origin: string, timeoutMs: number): Promise; waitForDaemonHealthy(origin: string, timeoutMs: number): Promise; @@ -81,7 +83,10 @@ export interface EnsureDaemonRunningDeps { } export interface DaemonCommandDeps { - ensureDaemonRunning(options: ParsedDaemonOptions): Promise; + ensureDaemonRunning( + options: ParsedDaemonOptions, + report?: DaemonStartupReporter, + ): Promise; startDaemonForeground(options: ParsedDaemonOptions): Promise; stdout: Pick; stderr: Pick; @@ -144,7 +149,9 @@ export async function handleDaemonCommand( return; } - const result = await deps.ensureDaemonRunning(parsed); + const result = await deps.ensureDaemonRunning(parsed, (message) => { + writeDaemonStartupStatus(deps.stdout, message); + }); if (result.status === 'already-running') { deps.stdout.write( `Kimi daemon already running at ${result.origin}${formatPid(result.pid)}.\n`, @@ -192,10 +199,13 @@ export function parseLogLevel(raw: string | undefined): DaemonLogLevel { export async function ensureDaemonRunning( options: ParsedDaemonOptions, deps: EnsureDaemonRunningDeps = DEFAULT_ENSURE_DAEMON_RUNNING_DEPS, + report: DaemonStartupReporter = () => {}, ): Promise { const origin = daemonOrigin(options.host, options.port); + report(`checking requested daemon at ${origin}`); if (await deps.isDaemonHealthy(origin, 1000)) { const lock = deps.readLiveDaemonLock(); + report(`requested daemon is healthy; using ${origin}`); return { status: 'already-running', origin, @@ -204,10 +214,17 @@ export async function ensureDaemonRunning( }; } + report('requested daemon is not healthy'); + report(`checking daemon lock at ${DEFAULT_LOCK_PATH}`); const lock = deps.readLiveDaemonLock(); if (lock !== undefined) { + report( + `found live daemon lock (pid ${lock.pid}, port ${lock.port}, started ${lock.started_at})`, + ); const lockOrigin = daemonOrigin(options.host, lock.port); + report(`checking locked daemon at ${lockOrigin}`); if (await deps.waitForDaemonHealthy(lockOrigin, 5000)) { + report(`locked daemon is healthy; reusing ${lockOrigin}`); return { status: 'already-running', origin: lockOrigin, @@ -215,15 +232,21 @@ export async function ensureDaemonRunning( logPath: deps.daemonLogPath(), }; } + report(`locked daemon did not become healthy at ${lockOrigin}`); + } else { + report('no live daemon lock found'); } + report(`starting daemon in background at ${origin}`); const started = deps.startDaemonBackground(options); const ready = await deps.waitForDaemonHealthy(origin, 15_000); if (!ready) { + report(`daemon did not become healthy at ${origin}`); throw new Error( `Kimi daemon did not become healthy at ${origin}. Check logs: ${started.logPath}`, ); } + report(`daemon is healthy at ${origin}`); return { status: 'started', origin, @@ -389,8 +412,16 @@ function formatPid(pid: number | undefined): string { return pid === undefined ? '' : ` (pid ${pid})`; } +function writeDaemonStartupStatus( + stdout: Pick, + message: string, +): void { + stdout.write(`Daemon startup: ${message}\n`); +} + const DEFAULT_DAEMON_COMMAND_DEPS: DaemonCommandDeps = { - ensureDaemonRunning, + ensureDaemonRunning: (options, report) => + ensureDaemonRunning(options, DEFAULT_ENSURE_DAEMON_RUNNING_DEPS, report), startDaemonForeground, stdout: process.stdout, stderr: process.stderr, diff --git a/apps/kimi-code/src/cli/sub/web.ts b/apps/kimi-code/src/cli/sub/web.ts index 8a35babc6..dd2b12a77 100644 --- a/apps/kimi-code/src/cli/sub/web.ts +++ b/apps/kimi-code/src/cli/sub/web.ts @@ -16,6 +16,7 @@ import { DEFAULT_DAEMON_PORT, ensureDaemonRunning, parsePort, + type DaemonStartupReporter, type EnsureDaemonResult, } from './daemon'; @@ -32,7 +33,7 @@ export interface WebCommandDeps { port: number; logLevel: 'info'; debugEndpoints: false; - }): Promise; + }, report?: DaemonStartupReporter): Promise; ensureDaemonWebReady(origin: string): Promise; openUrl(url: string): void; stdout: Pick; @@ -76,12 +77,17 @@ export async function handleWebCommand( } else { const host = opts.host ?? DEFAULT_DAEMON_HOST; const port = parsePort(opts.port, '--port', DEFAULT_DAEMON_PORT); - const daemon = await deps.ensureDaemonRunning({ - host, - port, - logLevel: 'info', - debugEndpoints: false, - }); + const daemon = await deps.ensureDaemonRunning( + { + host, + port, + logLevel: 'info', + debugEndpoints: false, + }, + (message) => { + deps.stdout.write(`Daemon startup: ${message}\n`); + }, + ); webOrigin = daemon.origin; if (daemon.status === 'started') { deps.stdout.write( @@ -134,7 +140,7 @@ export async function ensureDaemonWebReady(origin: string): Promise { } const DEFAULT_WEB_COMMAND_DEPS: WebCommandDeps = { - ensureDaemonRunning, + ensureDaemonRunning: (options, report) => ensureDaemonRunning(options, undefined, report), ensureDaemonWebReady, openUrl: defaultOpenUrl, stdout: process.stdout, diff --git a/apps/kimi-code/test/cli/daemon.test.ts b/apps/kimi-code/test/cli/daemon.test.ts index d9850da88..fbf6bc304 100644 --- a/apps/kimi-code/test/cli/daemon.test.ts +++ b/apps/kimi-code/test/cli/daemon.test.ts @@ -52,12 +52,15 @@ describe('kimi daemon', () => { await handleDaemonCommand({}, deps); - expect(deps.ensureDaemonRunning).toHaveBeenCalledWith({ - host: DEFAULT_DAEMON_HOST, - port: DEFAULT_DAEMON_PORT, - logLevel: 'info', - debugEndpoints: false, - }); + expect(deps.ensureDaemonRunning).toHaveBeenCalledWith( + { + host: DEFAULT_DAEMON_HOST, + port: DEFAULT_DAEMON_PORT, + logLevel: 'info', + debugEndpoints: false, + }, + expect.any(Function), + ); expect(deps.startDaemonForeground).not.toHaveBeenCalled(); expect(stderr.join('')).toBe(''); expect(stdout.join('')).toContain('Kimi daemon started in background'); @@ -76,6 +79,15 @@ describe('kimi daemon', () => { await handleDaemonCommand({}, deps); expect(deps.ensureDaemonRunning).toHaveBeenCalledTimes(1); + expect(deps.ensureDaemonRunning).toHaveBeenCalledWith( + { + host: DEFAULT_DAEMON_HOST, + port: DEFAULT_DAEMON_PORT, + logLevel: 'info', + debugEndpoints: false, + }, + expect.any(Function), + ); expect(deps.startDaemonForeground).not.toHaveBeenCalled(); expect(stderr.join('')).toBe(''); expect(stdout.join('')).toContain('Kimi daemon already running'); @@ -136,4 +148,46 @@ describe('kimi daemon', () => { 15_000, ); }); + + it('reports when a live daemon lock redirects to another daemon port', async () => { + const parsedOptions = { + host: DEFAULT_DAEMON_HOST, + port: 7999, + logLevel: 'info' as const, + debugEndpoints: false, + }; + const status: string[] = []; + const deps: EnsureDaemonRunningDeps = { + isDaemonHealthy: vi.fn(async () => false), + waitForDaemonHealthy: vi.fn(async () => true), + readLiveDaemonLock: vi.fn(() => ({ + pid: 4321, + started_at: '2026-06-10T00:00:00.000Z', + port: 7880, + })), + startDaemonBackground: vi.fn(() => ({ + pid: 9876, + logPath: '/tmp/kimi-daemon.log', + })), + daemonLogPath: vi.fn(() => '/tmp/kimi-daemon.log'), + }; + + await expect( + ensureDaemonRunning(parsedOptions, deps, (message) => status.push(message)), + ).resolves.toMatchObject({ + status: 'already-running', + origin: 'http://127.0.0.1:7880', + pid: 4321, + }); + + expect(status).toEqual([ + 'checking requested daemon at http://127.0.0.1:7999', + 'requested daemon is not healthy', + expect.stringContaining('checking daemon lock at '), + 'found live daemon lock (pid 4321, port 7880, started 2026-06-10T00:00:00.000Z)', + 'checking locked daemon at http://127.0.0.1:7880', + 'locked daemon is healthy; reusing http://127.0.0.1:7880', + ]); + expect(deps.startDaemonBackground).not.toHaveBeenCalled(); + }); }); diff --git a/apps/kimi-code/test/cli/web.test.ts b/apps/kimi-code/test/cli/web.test.ts index 5a3bdf003..e71786c0c 100644 --- a/apps/kimi-code/test/cli/web.test.ts +++ b/apps/kimi-code/test/cli/web.test.ts @@ -52,12 +52,15 @@ describe('kimi web', () => { await handleWebCommand({}, deps); - expect(deps.ensureDaemonRunning).toHaveBeenCalledWith({ - host: DEFAULT_DAEMON_HOST, - port: DEFAULT_DAEMON_PORT, - logLevel: 'info', - debugEndpoints: false, - }); + expect(deps.ensureDaemonRunning).toHaveBeenCalledWith( + { + host: DEFAULT_DAEMON_HOST, + port: DEFAULT_DAEMON_PORT, + logLevel: 'info', + debugEndpoints: false, + }, + expect.any(Function), + ); expect(deps.ensureDaemonWebReady).toHaveBeenCalledWith(DEFAULT_DAEMON_ORIGIN); expect(deps.openUrl).toHaveBeenCalledWith(DEFAULT_DAEMON_ORIGIN); expect(stderr.join('')).toBe(''); @@ -85,16 +88,47 @@ describe('kimi web', () => { await handleWebCommand({ host: '0.0.0.0', port: '8899' }, deps); - expect(deps.ensureDaemonRunning).toHaveBeenCalledWith({ - host: '0.0.0.0', - port: 8899, - logLevel: 'info', - debugEndpoints: false, - }); + expect(deps.ensureDaemonRunning).toHaveBeenCalledWith( + { + host: '0.0.0.0', + port: 8899, + logLevel: 'info', + debugEndpoints: false, + }, + expect.any(Function), + ); expect(deps.ensureDaemonWebReady).toHaveBeenCalledWith('http://127.0.0.1:8899'); expect(deps.openUrl).toHaveBeenCalledWith('http://127.0.0.1:8899'); }); + it('prints daemon startup trace from the local daemon resolver', async () => { + const { deps, stdout } = makeDeps({ + ensureDaemonRunning: vi.fn(async (_options, report) => { + report?.('checking requested daemon at http://127.0.0.1:7999'); + report?.('found live daemon lock (pid 4321, port 7880, started 2026-06-10T00:00:00.000Z)'); + report?.('locked daemon is healthy; reusing http://127.0.0.1:7880'); + return { + status: 'already-running' as const, + origin: 'http://127.0.0.1:7880', + pid: 4321, + }; + }), + }); + + await handleWebCommand({ port: '7999', open: false }, deps); + + expect(stdout.join('')).toContain( + 'Daemon startup: checking requested daemon at http://127.0.0.1:7999', + ); + expect(stdout.join('')).toContain( + 'Daemon startup: found live daemon lock (pid 4321, port 7880, started 2026-06-10T00:00:00.000Z)', + ); + expect(stdout.join('')).toContain( + 'Daemon startup: locked daemon is healthy; reusing http://127.0.0.1:7880', + ); + expect(stdout.join('')).toContain('Kimi web: http://127.0.0.1:7880'); + }); + it('respects --no-open while still printing the daemon-hosted web URL', async () => { const { deps, stdout } = makeDeps();