mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
fix(local-model): ask the launch guard before every start of an agent on the managed endpoint
Only the two session routes asked launchRefusalMessage(). The attach that finds no pane, the respawn of a dead pane (a click on the tab, a respawn start, the boot recovery), restartCli() and the reboot-restore rebuild started an agent on the endpoint unasked. Session gains setLaunchGuard(), asked right before a NEW agent process is started and never for an attach to an agent that runs; WebServer.setupSessionListeners() sets it for every session; a refusal is a LaunchRefusedError that never falls back to a direct PTY. The reboot-restore route asks before it builds and skips the entry with its own reason, left on offer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
fa978008d2
commit
3c4bb91d6f
7 files changed
+380
-2
No files matched your search
@@ -40,6 +40,8 @@ function rebootSkipReason(reason) {
|
||||
return 'already open';
|
||||
case 'capacity-reached':
|
||||
return 'session limit reached';
|
||||
case 'local-model-not-ready':
|
||||
return 'the local model is not ready';
|
||||
case 'rebuild-failed':
|
||||
return 'the agent would not start';
|
||||
default:
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
isWorkingDirAllowedForUsername,
|
||||
sessionCapacityMessage,
|
||||
} from '../route-helpers.js';
|
||||
import { getLocalModelService } from '../../local-model/install-job.js';
|
||||
import { rebootRestoreRegistry } from '../reboot-restore-registry.js';
|
||||
import { rejectAlreadyLive, type RebootRestoreEntry, type RebootRestoreRejection } from '../../reboot-restore.js';
|
||||
import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
|
||||
@@ -163,6 +164,21 @@ export function registerRebootRestoreRoutes(app: FastifyInstance, ctx: RebootRes
|
||||
failures.push({ sessionId: entry.sessionId, reason: 'workspace-forbidden' });
|
||||
continue;
|
||||
}
|
||||
// A session that ran on the model Codeman runs on this Mac is rebuilt only while that
|
||||
// model can be started on: its own runtime is up and its digest is the one stored at
|
||||
// install time. Asked before anything is built, like the two session routes; the
|
||||
// session's own launch guard would refuse the start too, but only after the session
|
||||
// exists. Every other endpoint, and a session without one, is answered `undefined`.
|
||||
const savedModel = (entry.state as { __customModel?: { endpointId?: string; modelId?: string } }).__customModel;
|
||||
if (savedModel && typeof savedModel.endpointId === 'string' && typeof savedModel.modelId === 'string') {
|
||||
const refusal = await getLocalModelService().launchRefusalMessage(savedModel.endpointId, savedModel.modelId);
|
||||
if (refusal) {
|
||||
console.warn(`[reboot-restore] ${entry.sessionId} is not rebuilt: ${refusal}`);
|
||||
// Left on offer: the model can be started (or installed again) and the click repeated.
|
||||
failures.push({ sessionId: entry.sessionId, reason: 'local-model-not-ready' });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const saved = entry.state;
|
||||
const claudeModeConfig = await ctx.getClaudeModeConfig();
|
||||
|
||||
@@ -2194,6 +2194,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
|
||||
|
||||
Reference in new issue
Block a user