From 84c10174878f4828632cc968e1b9c0fe81d9d1c4 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 6 Nov 2026 20:23:58 +0100 Subject: [PATCH] feat(local-model): the launch refusal hook, and an honest name for a rebuilt log line - `LocalModelService.launchRefusal(endpointId)`: what a session launch asks before it starts on the managed endpoint. It answers the error that refuses the launch (the runtime child is not alive, another program holds the port, the runtime keeps stopping, the model's digest changed) or nothing. New error code `runtime-not-running` with its sentence, for a runtime the person stopped: only a refused session start says it. - The fake runtime's MacBook log line is rebuilt from the two values the measurement notes quote, so it is `INFERENCE_COMPUTE_LINE_MACBOOK` now. Only the Mac mini line is a verbatim capture and keeps `REAL_`. Co-Authored-By: Claude Fable 5.1 --- src/local-model/install-job.ts | 11 +++++++++++ src/types/local-model-install.ts | 6 ++++++ test/fixtures/fake-ollama.ts | 10 +++++----- test/local-model-fake-ollama.test.ts | 4 ++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/local-model/install-job.ts b/src/local-model/install-job.ts index 44098e9b..b518836a 100644 --- a/src/local-model/install-job.ts +++ b/src/local-model/install-job.ts @@ -84,6 +84,7 @@ import type { LocalModelConflict, LocalModelDeveloperTools, LocalModelErrorCode, + LocalModelInstallError, LocalModelInstallRequest, LocalModelInstallState, LocalModelInstallStep, @@ -458,6 +459,16 @@ export interface LocalModelServiceDeps { export interface LocalModelService { /** The `install` object of the GET. Never throws. */ view(): Promise; + /** + * Whether a session may be started on the saved endpoint `endpointId` right now. `undefined`: + * yes, which is also the answer for every endpoint that is not this feature's. Otherwise the + * error that refuses the launch, in this order: `runtime-not-running` (the runtime child + * Codeman started is not alive, so whatever answers on the port is not ours), + * `port-in-use`, `runtime-stopped`, `model-changed` (the digest is read again and is not the + * stored one). The session launch path asks this before it starts a session with the + * managed endpoint, and `boot()` asks it once. Never throws. + */ + launchRefusal(endpointId: string): Promise; /** What the GET passes to `recommend()`: the stored measured total and the runtime download that is still needed. */ recommendOptions(): Promise; /** `POST /api/local-model/install`. Answers once the job is STARTED; the job runs on in the server. */ diff --git a/src/types/local-model-install.ts b/src/types/local-model-install.ts index 5d625d7a..d964e53a 100644 --- a/src/types/local-model-install.ts +++ b/src/types/local-model-install.ts @@ -116,6 +116,8 @@ export const LOCAL_MODEL_INSTALL_STEPS: readonly LocalModelInstallStep[] = [ * | `runtime-unpack-failed` | `/usr/bin/tar` failed | * | `runtime-start-failed` | the runtime was started and never answered `/api/version` | * | `runtime-stopped` | installed: the runtime crashed and was restarted 3 times, then given up | + * | `runtime-not-running` | installed: the runtime is not running (the person stopped it). Only a | + * | | refused session start says it; the view shows no error for a stopped runtime | * | `port-in-use` | another program listens on the stored port, or no port of the span is free | * | `disk-full` | a write failed for lack of space | * | `pull-failed` | the pull still ends on an error line or a cut stream after its retries | @@ -137,6 +139,7 @@ export type LocalModelErrorCode = | 'runtime-unpack-failed' | 'runtime-start-failed' | 'runtime-stopped' + | 'runtime-not-running' | 'port-in-use' | 'disk-full' | 'pull-failed' @@ -218,6 +221,7 @@ export const LOCAL_MODEL_INSTALL_LABELS = { errRuntimeUnpackFailed: 'The runtime could not be unpacked on this Mac.', errRuntimeStartFailed: 'The runtime did not start on this Mac.', errRuntimeStopped: 'The runtime keeps stopping, so Codeman no longer restarts it.', + errRuntimeNotRunning: 'The model on this Mac is not running. Start it first.', errPortInUse: 'Port {port} is in use by another program.', errDiskFull: 'The disk is full. Free up space, then try again.', errPullFailed: 'The download keeps stopping. The network may be damaging data. Try again or use another network.', @@ -259,6 +263,7 @@ export const LOCAL_MODEL_ERROR_LABELS: Record { describe('the fake runtime: the child process', () => { it('holds the real log lines with the device total', () => { expect(REAL_INFERENCE_COMPUTE_LINE_MAC_MINI).toContain('total="11.8 GiB" available="11.8 GiB"'); - expect(REAL_INFERENCE_COMPUTE_LINE_MACBOOK).toContain('total="37.4 GiB" available="37.4 GiB"'); + expect(INFERENCE_COMPUTE_LINE_MACBOOK).toContain('total="37.4 GiB" available="37.4 GiB"'); expect(MACBOOK_MEASURED_TOTAL_BYTES).toBe(Math.floor(37.4 * 1024 ** 3)); expect(fakeRuntimeLog('37.4 GiB')[2]).toContain('msg="inference compute"'); });