diff --git a/packages/cli/src/commands/handlers/default.ts b/packages/cli/src/commands/handlers/default.ts index 78e50369f7..5e5509ceb0 100644 --- a/packages/cli/src/commands/handlers/default.ts +++ b/packages/cli/src/commands/handlers/default.ts @@ -18,6 +18,12 @@ export default Runtime.handler(Commands, (input) => const server = yield* Server.resolve({ server: Option.getOrUndefined(input.server), standalone: input.standalone, + onStart: (reason) => + process.stderr.write( + reason === "version-mismatch" + ? "Restarting background server (version mismatch)...\n" + : "Starting background server...\n", + ), }) const config = TuiConfig.resolve({}, { terminalSuspend: false }) let disposeSlots: (() => void) | undefined diff --git a/packages/cli/src/services/server.ts b/packages/cli/src/services/server.ts index 5ea8434792..34ddcf965d 100644 --- a/packages/cli/src/services/server.ts +++ b/packages/cli/src/services/server.ts @@ -11,6 +11,7 @@ export type Args = { readonly server?: string readonly standalone?: boolean readonly mismatch?: "replace" | "ignore" | "error" + readonly onStart?: Service.StartOptions["onStart"] } export type Resolved = { @@ -46,7 +47,7 @@ export const resolve = Effect.fn("cli.server.resolve")(function* (args: Args) { } const options = yield* ServiceConfig.options() - const endpoint = yield* resolveManaged(options, args.mismatch ?? "replace") + const endpoint = yield* resolveManaged({ ...options, onStart: args.onStart }, args.mismatch ?? "replace") const reconnectOptions = { ...options, version: undefined } return { endpoint, @@ -70,7 +71,7 @@ export const resolve = Effect.fn("cli.server.resolve")(function* (args: Args) { }) const resolveManaged = Effect.fnUntraced(function* ( - options: Service.Options, + options: Service.StartOptions, mismatch: NonNullable, ) { if (mismatch === "replace") return yield* Service.start(options) diff --git a/packages/client/src/effect/service.ts b/packages/client/src/effect/service.ts index 3e5d3ec0f2..de98066a09 100644 --- a/packages/client/src/effect/service.ts +++ b/packages/client/src/effect/service.ts @@ -32,6 +32,12 @@ export type Options = { readonly command?: ReadonlyArray } +export type StartReason = "missing" | "version-mismatch" + +export type StartOptions = Options & { + readonly onStart?: (reason: StartReason) => void +} + // Read-only lookup: registration file plus health check and version gate. // Never spawns; escalation to start() is the caller's policy. export const discover = Effect.fn("service.discover")(function* (options: Options = {}) { @@ -47,10 +53,11 @@ const discoverLocal = Effect.fnUntraced(function* (options: Options) { // Idempotent ensure-running: reuses a healthy compatible server, replaces a // version-mismatched one, and otherwise spawns the service command detached. -export const start = Effect.fn("service.start")(function* (options: Options = {}) { +export const start = Effect.fn("service.start")(function* (options: StartOptions = {}) { const compatible = yield* discover(options) if (compatible !== undefined) return compatible const mismatched = yield* find(options) + yield* Effect.sync(() => options.onStart?.(mismatched === undefined ? "missing" : "version-mismatch")) if (mismatched !== undefined) yield* kill(mismatched.info, options).pipe(Effect.ignore) const [command, ...args] = options.command ?? ["opencode", "serve", "--service"]