Merge feat/local-model-recommendation: recommend and install a local coding model on this Mac (experimental)

Conflicts resolved as on test/desktop-macbook: config/test-suites.ts keeps both browser tests (desktop-connect-page and local-model-ui), and the CLAUDE.md state-files line keeps #778's cli-installed entry with the local model's local-model.json and local-model/ entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-08 04:06:46 +01:00
commit e8a63ede00
80 files changed
+31268 -261

No files matched your search

+29
View File
@@ -147,6 +147,7 @@ import { probeLocalPaths, readClaudeEditSources, resolveClaudeTranscript } from
import { remoteLocateClaudeTranscript, remoteProbePaths, remoteReadClaudeEditSources } from '../remote-files.js';
import { remoteTmuxLocation } from '../tmux-manager.js';
import { stopDeepSeekWeb } from '../deepseek-web-server.js';
import { getLocalModelService } from '../local-model/install-job.js';
import {
wireRespawnListeners,
setupTimedRespawn,
@@ -265,6 +266,7 @@ import {
pruneIdleLlamaSwapLogTails,
tryWebviewRefererFallback,
registerCliRegistryRoutes,
registerLocalModelRoutes,
} from './routes/index.js';
import { isLostWebviewFrameNavigation } from './webview-proxy.js';
import { CronService } from '../cron/cron-service.js';
@@ -1418,6 +1420,7 @@ export class WebServer extends EventEmitter {
registerDoctorRoutes(this.app);
registerCustomModelRoutes(this.app);
registerCliRegistryRoutes(this.app);
registerLocalModelRoutes(this.app, ctx);
// Cron: build the service from the same context, recompute
// due times for any persisted jobs, then expose it to its routes.
@@ -2192,6 +2195,17 @@ export class WebServer extends EventEmitter {
// First keep-warm evaluation: publishes the effective opt-in for the Session Options
// readout even before any cache report arrives.
void this.keepWarmService.schedule(session.id);
// Before every start of a NEW agent process the session asks this (a fresh pane, the
// respawn of a dead one, a restart in place; never an attach to an agent that runs).
// The model Codeman runs on this Mac is the one endpoint with something to say: no agent
// is started on it while its runtime is down or its model is not the one that was
// installed. Every other session, and every other endpoint, is answered `undefined`.
// Set on every call, above the idempotency return: it holds no state of its own.
session.setLaunchGuard(async () => {
const selection = session.customModel;
if (!selection) return undefined;
return getLocalModelService().launchRefusalMessage(selection.endpointId, selection.modelId);
});
// Idempotent: the wiring exit handler detaches ALL listeners on every PTY exit
// (removeSessionListenerRefs), so the re-attach routes (/interactive,
// /interactive-respawn, /shell) call this again to restore observability
@@ -3472,6 +3486,14 @@ export class WebServer extends EventEmitter {
}
}
// The model Codeman runs on this Mac (src/local-model/): a job a restart interrupted comes
// back paused and waits for a click, and an installed runtime that was not stopped is
// started again (an own orphan on its port is adopted or replaced). Off the startup path:
// boot() never throws, and nothing waits for a runtime to answer. Not in tests.
if (!this.testMode) {
void getLocalModelService().boot();
}
// Sweep agent preamble caches whose sessions are gone (see
// pruneAgentSessionPreambles). Once per boot, after restore, so every session this
// instance owns is in the keep set. Best-effort and off the startup critical path.
@@ -4621,6 +4643,13 @@ export class WebServer extends EventEmitter {
// got wrong once.
void stopDeepSeekWeb();
// The local model runtime is a non-detached child, but a child is not ended by its
// parent's exit: it gets SIGTERM here, and an install job in flight is interrupted (the
// next start reads it as paused). An orphan a hard kill leaves is adopted at the next boot.
if (!this.testMode) {
void getLocalModelService().shutdown();
}
// The header's GPU / CPU clock sampler is a Windows child process under WSL, and its idle sweep a timer.
stopHostSensors();