mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
feat(custom-model): detect and notify when a session's model gets swapped out later
The llama-swap conflict check on the apply/create routes only ever runs at THAT session's own launch/apply moment, and cannot see a swap caused by a DIFFERENT session's later, ordinary use. Confirmed live: a second Codex session picking a different model launched with no warning at all — nothing conflicted at that exact instant — yet it silently evicted the first session's model regardless (llama.cpp runs one model at a time). Reproduced and root-caused via direct API calls against a live test-picker instance rather than guessing. - detectCustomModelSwapDisplacements() (custom-model-routes.ts): groups live sessions with a customModel by endpointId, checks each group's endpoint via GET /running once, and flags a session whose own modelId is no longer in the running list. Read-only, best-effort per endpoint like refreshAllCustomModelHosts's sibling sweep. - Notifies once per displacement via a caller-owned de-dupe Set: a session id is added when displaced, removed once its own model is loaded/ready again, so a later genuinely-new displacement can notify again. - New periodic sweep in server.ts (CUSTOM_MODEL_SWAP_CHECK_INTERVAL_MS, 20s — much shorter than the 5-minute model-list refresh, since this is time-sensitive) broadcasts a new custom-model:swapped-out SSE event per displacement. De-dupe Set cleared per-session on session cleanup to avoid an unbounded leak. - Frontend: global toast (not tied to the displaced session's tab, since the point is warning before the user types into it) naming the session, its previous model, and what's currently loaded. Chose the "detect after the fact" scope (vs. checking before every message send, which would add a round-trip to every turn on every custom-model session) per explicit user decision after being presented the trade-off. 9 new tests for the detection logic (flag/clear/re-flag cycle, unreachable/deleted endpoints, non-llama-swap servers, multiple sessions on one endpoint). SSE registry bumped 158->159, parity test passing. Typecheck/lint/frontend-syntax clean; full suite shows no new regressions (9 more passing than baseline, matching the new tests; same pre-existing Windows-environment failures). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RqZeHrRS6DYcGcGX2p9EwG
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
470f75b08c
commit
5ddc028a2f
@@ -191,6 +191,7 @@ import {
|
||||
registerTabLayoutRoutes,
|
||||
registerCustomModelRoutes,
|
||||
refreshAllCustomModelHosts,
|
||||
detectCustomModelSwapDisplacements,
|
||||
tryWebviewRefererFallback,
|
||||
} from './routes/index.js';
|
||||
import { isLostWebviewFrameNavigation } from './webview-proxy.js';
|
||||
@@ -204,6 +205,12 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const SSE_CLIENT_ID_RE = /^[A-Za-z0-9_-]{8,64}$/;
|
||||
const CODEX_USAGE_POLL_INTERVAL_MS = 5 * 60_000;
|
||||
const CUSTOM_MODEL_REDISCOVER_INTERVAL_MS = 5 * 60_000;
|
||||
// Much shorter than the model-LIST refresh above on purpose: this catches an actual
|
||||
// eviction (a session's model no longer loaded, silently swapped out by another
|
||||
// session's use), which the user wants to know about promptly, not once every 5
|
||||
// minutes. Cheap either way — one /running GET per distinct endpoint with at least
|
||||
// one live custom-model session, not per session.
|
||||
const CUSTOM_MODEL_SWAP_CHECK_INTERVAL_MS = 20_000;
|
||||
|
||||
function escapeHtmlText(value: string): string {
|
||||
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
@@ -281,6 +288,8 @@ export class WebServer extends EventEmitter {
|
||||
// Store session listener references for explicit cleanup (prevents memory leaks)
|
||||
private sessionListenerRefs: Map<string, SessionListenerRefs> = new Map();
|
||||
private scheduledRuns: Map<string, ScheduledRun> = new Map();
|
||||
/** De-dupe state for the swap-displacement sweep — see detectCustomModelSwapDisplacements. */
|
||||
private _customModelDisplacedNotified: Set<string> = new Set();
|
||||
/** Cron service (assigned in setupRoutes). */
|
||||
private cronService!: CronService;
|
||||
private sse: SseStreamManager;
|
||||
@@ -1317,6 +1326,10 @@ export class WebServer extends EventEmitter {
|
||||
session.ralphTracker.stopWatchingFixPlan();
|
||||
}
|
||||
|
||||
// Custom Model Endpoint Profiles: drop this session's swap-displacement notify flag
|
||||
// (see _checkCustomModelSwapDisplacements below) so it can't linger in that Set forever.
|
||||
this._customModelDisplacedNotified.delete(sessionId);
|
||||
|
||||
// Kill all subagents spawned by this session (scoped to sessionId to avoid cross-session kills)
|
||||
if (session && killMux) {
|
||||
try {
|
||||
@@ -2755,6 +2768,32 @@ export class WebServer extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
// Custom Model Endpoint Profiles: the swap-conflict check on the apply/create routes
|
||||
// only ever runs at THAT session's own launch/apply moment — it cannot catch a LATER
|
||||
// eviction triggered by a different session's normal use, since llama-swap has no push
|
||||
// notification of its own and only swaps in response to a real inference request
|
||||
// (confirmed live: a session created while nothing else conflicted at that instant can
|
||||
// still get silently displaced afterward). This periodic sweep is what catches that
|
||||
// case after the fact and tells the displaced session's user, rather than leaving them
|
||||
// to discover it only when their next prompt behaves unexpectedly.
|
||||
if (!this.testMode) {
|
||||
this.cleanup.setInterval(
|
||||
() => {
|
||||
detectCustomModelSwapDisplacements(this.sessions.values(), this._customModelDisplacedNotified)
|
||||
.then((displacements) => {
|
||||
for (const displacement of displacements) {
|
||||
this.broadcast(SseEvent.CustomModelSwappedOut, displacement);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[custom-model] swap-displacement check failed:', getErrorMessage(err));
|
||||
});
|
||||
},
|
||||
CUSTOM_MODEL_SWAP_CHECK_INTERVAL_MS,
|
||||
{ description: 'custom model swap-displacement check' }
|
||||
);
|
||||
}
|
||||
|
||||
// Start scheduled runs cleanup timer
|
||||
this.cleanup.setInterval(
|
||||
() => {
|
||||
|
||||
Reference in New Issue
Block a user