aicodeman 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/cli-registry/schema.d.ts +8 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +29 -0
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +62 -7
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +62 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/config/remote-wake-limits.d.ts +23 -0
- package/dist/config/remote-wake-limits.d.ts.map +1 -0
- package/dist/config/remote-wake-limits.js +23 -0
- package/dist/config/remote-wake-limits.js.map +1 -0
- package/dist/custom-model-hosts.d.ts +32 -0
- package/dist/custom-model-hosts.d.ts.map +1 -1
- package/dist/custom-model-hosts.js.map +1 -1
- package/dist/custom-model-injection-apply.d.ts +29 -1
- package/dist/custom-model-injection-apply.d.ts.map +1 -1
- package/dist/custom-model-injection-apply.js +191 -5
- package/dist/custom-model-injection-apply.js.map +1 -1
- package/dist/custom-model-injection.d.ts +35 -9
- package/dist/custom-model-injection.d.ts.map +1 -1
- package/dist/custom-model-injection.js +37 -16
- package/dist/custom-model-injection.js.map +1 -1
- package/dist/mux-interface.d.ts +12 -0
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/remote-hosts.d.ts +20 -0
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +33 -0
- package/dist/remote-hosts.js.map +1 -1
- package/dist/remote-wake.d.ts +446 -0
- package/dist/remote-wake.d.ts.map +1 -0
- package/dist/remote-wake.js +880 -0
- package/dist/remote-wake.js.map +1 -0
- package/dist/session-env-clamp.d.ts +12 -7
- package/dist/session-env-clamp.d.ts.map +1 -1
- package/dist/session-env-clamp.js +12 -7
- package/dist/session-env-clamp.js.map +1 -1
- package/dist/session-submit-verifier.d.ts +44 -0
- package/dist/session-submit-verifier.d.ts.map +1 -0
- package/dist/session-submit-verifier.js +122 -0
- package/dist/session-submit-verifier.js.map +1 -0
- package/dist/session.d.ts +9 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +42 -1
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +9 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/session.d.ts +26 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.8df358cd.js +38 -0
- package/dist/web/public/app.8df358cd.js.br +0 -0
- package/dist/web/public/app.8df358cd.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.258b140f.js → constants.85ed12f3.js} +74 -0
- package/dist/web/public/constants.85ed12f3.js.br +0 -0
- package/dist/web/public/constants.85ed12f3.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js +440 -0
- package/dist/web/public/host-wake-ui.js.br +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.83924614.js +1 -0
- package/dist/web/public/i18n.83924614.js.br +0 -0
- package/dist/web/public/i18n.83924614.js.gz +0 -0
- package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
- package/dist/web/public/index.html +186 -10
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
- package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.6caaa28a.css.gz +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.5b07ad14.js → panels-ui.28df5053.js} +5 -5
- package/dist/web/public/panels-ui.28df5053.js.br +0 -0
- package/dist/web/public/panels-ui.28df5053.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.d4aea71a.js +88 -0
- package/dist/web/public/session-ui.d4aea71a.js.br +0 -0
- package/dist/web/public/session-ui.d4aea71a.js.gz +0 -0
- package/dist/web/public/settings-ui.4762f480.js +78 -0
- package/dist/web/public/settings-ui.4762f480.js.br +0 -0
- package/dist/web/public/settings-ui.4762f480.js.gz +0 -0
- package/dist/web/public/styles.e34fea04.css +1 -0
- package/dist/web/public/styles.e34fea04.css.br +0 -0
- package/dist/web/public/styles.e34fea04.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/terminal-ui.35812aa2.js +2 -0
- package/dist/web/public/terminal-ui.35812aa2.js.br +0 -0
- package/dist/web/public/terminal-ui.35812aa2.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/custom-model-routes.d.ts +173 -0
- package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
- package/dist/web/routes/custom-model-routes.js +628 -11
- package/dist/web/routes/custom-model-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +1 -1
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -1
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +6 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +448 -17
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +15 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +76 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +21 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +130 -5
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +25 -2
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +30 -3
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +75 -26
- package/skills/codeman/preamble.sh +66 -19
- package/skills/codeman/reference/recipes.md +1 -1
- package/dist/web/public/app.6d2dc4e8.js +0 -38
- package/dist/web/public/app.6d2dc4e8.js.br +0 -0
- package/dist/web/public/app.6d2dc4e8.js.gz +0 -0
- package/dist/web/public/constants.258b140f.js.br +0 -0
- package/dist/web/public/constants.258b140f.js.gz +0 -0
- package/dist/web/public/i18n.5f897ed5.js +0 -1
- package/dist/web/public/i18n.5f897ed5.js.br +0 -0
- package/dist/web/public/i18n.5f897ed5.js.gz +0 -0
- package/dist/web/public/panels-ui.5b07ad14.js.br +0 -0
- package/dist/web/public/panels-ui.5b07ad14.js.gz +0 -0
- package/dist/web/public/session-ui.42b81477.js +0 -77
- package/dist/web/public/session-ui.42b81477.js.br +0 -0
- package/dist/web/public/session-ui.42b81477.js.gz +0 -0
- package/dist/web/public/settings-ui.58f1d756.js +0 -67
- package/dist/web/public/settings-ui.58f1d756.js.br +0 -0
- package/dist/web/public/settings-ui.58f1d756.js.gz +0 -0
- package/dist/web/public/styles.e6edfb0b.css +0 -1
- package/dist/web/public/styles.e6edfb0b.css.br +0 -0
- package/dist/web/public/styles.e6edfb0b.css.gz +0 -0
- package/dist/web/public/terminal-ui.4f8d5820.js +0 -2
- package/dist/web/public/terminal-ui.4f8d5820.js.br +0 -0
- package/dist/web/public/terminal-ui.4f8d5820.js.gz +0 -0
package/dist/web/server.js
CHANGED
|
@@ -42,6 +42,7 @@ import { hostname as getHostname, uptime as osUptime } from 'node:os';
|
|
|
42
42
|
import { looksLikeHostReboot, newestPersistedActivity, planRebootRestore } from '../reboot-restore.js';
|
|
43
43
|
import { rebootRestoreRegistry } from './reboot-restore-registry.js';
|
|
44
44
|
import { dataPath, getDataDir, CODEMAN_INSTANCE } from '../config/instance.js';
|
|
45
|
+
import { readRemoteHosts, rehydrateRemoteHostFields } from '../remote-hosts.js';
|
|
45
46
|
import { normalizeBasePath, stripBasePath, joinBasePath } from '../config/base-path.js';
|
|
46
47
|
import { GLYPH, palette } from '../cli-style.js';
|
|
47
48
|
import { getHookSecret } from '../config/hook-secret.js';
|
|
@@ -58,7 +59,7 @@ import { subagentWatcher, } from '../subagent-watcher.js';
|
|
|
58
59
|
import { imageWatcher } from '../image-watcher.js';
|
|
59
60
|
import { workflowRunWatcher, summarizeRun } from '../workflow-run-watcher.js';
|
|
60
61
|
import { attachmentRegistry, buildFileThumbnailRoute, registerExternalAttachment } from '../attachment-registry.js';
|
|
61
|
-
import { getCli } from '../config/cli-registry/registry.js';
|
|
62
|
+
import { getCli, enabledClis } from '../config/cli-registry/registry.js';
|
|
62
63
|
import { readCustomModelHosts } from '../custom-model-hosts.js';
|
|
63
64
|
import { applyCustomModelInjection, customModelConfigDir, removeConfigDir } from '../custom-model-injection-apply.js';
|
|
64
65
|
import { registerGeneratedArtifactAttachment } from '../generated-artifact-attachments.js';
|
|
@@ -113,7 +114,7 @@ import { isMultiUserMode } from '../config/multiuser.js';
|
|
|
113
114
|
import { bootstrapInitialAdmin, hasUsers, resolveClaudeModeForUsername } from '../user-store.js';
|
|
114
115
|
import { installRouteErrorHandler } from './route-error-handler.js';
|
|
115
116
|
import { isExplicitlyEnabled, isLoopbackBindHost, buildHostPolicy } from './network-auth-policy.js';
|
|
116
|
-
import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerScheduledRoutes, registerHookEventRoutes, registerApprovalRoutes, registerRebootRestoreRoutes, registerReadMyMindRoutes, registerStatusTelemetryRoutes, registerSystemRoutes, registerCaseRoutes, registerSessionRoutes, registerRespawnRoutes, registerRalphRoutes, registerPlanRoutes, registerClipboardRoutes, registerSearchRoutes, registerOrchestratorRoutes, registerCronRoutes, registerMeRoutes, registerAdminRoutes, registerWsRoutes, registerVoiceRoutes, registerWebviewRoutes, registerTabLayoutRoutes, registerCustomModelRoutes, tryWebviewRefererFallback, } from './routes/index.js';
|
|
117
|
+
import { registerPushRoutes, registerTeamRoutes, registerMuxRoutes, registerFileRoutes, registerScheduledRoutes, registerHookEventRoutes, registerApprovalRoutes, registerRebootRestoreRoutes, registerReadMyMindRoutes, registerStatusTelemetryRoutes, registerSystemRoutes, registerCaseRoutes, registerSessionRoutes, registerRespawnRoutes, registerRalphRoutes, registerPlanRoutes, registerClipboardRoutes, registerSearchRoutes, registerOrchestratorRoutes, registerCronRoutes, registerMeRoutes, registerAdminRoutes, registerWsRoutes, registerVoiceRoutes, registerWebviewRoutes, registerTabLayoutRoutes, registerCustomModelRoutes, refreshAllCustomModelHosts, readCustomModelEndpointsEnabled, closeAllLlamaSwapLogTails, detectCustomModelSwapDisplacements, pruneIdleLlamaSwapLogTails, tryWebviewRefererFallback, } from './routes/index.js';
|
|
117
118
|
import { isLostWebviewFrameNavigation } from './webview-proxy.js';
|
|
118
119
|
import { CronService } from '../cron/cron-service.js';
|
|
119
120
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -122,9 +123,29 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
122
123
|
// while capping growth of `sseClientsById` and blocking pathological inputs.
|
|
123
124
|
const SSE_CLIENT_ID_RE = /^[A-Za-z0-9_-]{8,64}$/;
|
|
124
125
|
const CODEX_USAGE_POLL_INTERVAL_MS = 5 * 60_000;
|
|
126
|
+
const CUSTOM_MODEL_REDISCOVER_INTERVAL_MS = 5 * 60_000;
|
|
127
|
+
// Much shorter than the model-LIST refresh above on purpose: this catches an actual
|
|
128
|
+
// eviction (a session's model no longer loaded, silently swapped out by another
|
|
129
|
+
// session's use), which the user wants to know about promptly, not once every 5
|
|
130
|
+
// minutes. Cheap either way — one /running GET per distinct endpoint with at least
|
|
131
|
+
// one live custom-model session, not per session.
|
|
132
|
+
const CUSTOM_MODEL_SWAP_CHECK_INTERVAL_MS = 20_000;
|
|
125
133
|
function escapeHtmlText(value) {
|
|
126
134
|
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
127
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Escapes a JSON string for safe embedding as the body of an inline `<script>`
|
|
138
|
+
* tag: `<` becomes the six-character sequence `<`, which both a JSON
|
|
139
|
+
* parser and a plain JS string literal decode back to `<` (both treat
|
|
140
|
+
* `\uXXXX` identically), but which can never itself form the two literal
|
|
141
|
+
* characters `<` `/` a browser's HTML tokenizer looks for to end the tag. A
|
|
142
|
+
* value containing a literal `</script>` would otherwise close the tag early
|
|
143
|
+
* and turn the rest of the document into inert script-body text. Exported so
|
|
144
|
+
* it unit-tests without constructing a WebServer (which needs a real tmux).
|
|
145
|
+
*/
|
|
146
|
+
export function escapeScriptJson(json) {
|
|
147
|
+
return json.replace(/</g, '\\u003c');
|
|
148
|
+
}
|
|
128
149
|
import { SESSIONS_LIST_CACHE_TTL, SCHEDULED_CLEANUP_INTERVAL, SCHEDULED_RUN_MAX_AGE, SSE_HEARTBEAT_INTERVAL, SESSION_LIMIT_WAIT_MS, ITERATION_PAUSE_MS, STATS_COLLECTION_INTERVAL_MS, INACTIVITY_TIMEOUT_MS, CRON_TICK_INTERVAL, } from '../config/server-timing.js';
|
|
129
150
|
/**
|
|
130
151
|
* Get or generate a self-signed TLS certificate for HTTPS.
|
|
@@ -163,8 +184,18 @@ export class WebServer extends EventEmitter {
|
|
|
163
184
|
// Store session listener references for explicit cleanup (prevents memory leaks)
|
|
164
185
|
sessionListenerRefs = new Map();
|
|
165
186
|
scheduledRuns = new Map();
|
|
187
|
+
/** De-dupe state for the swap-displacement sweep — see detectCustomModelSwapDisplacements. */
|
|
188
|
+
_customModelDisplacedNotified = new Set();
|
|
166
189
|
/** Cron service (assigned in setupRoutes). */
|
|
167
190
|
cronService;
|
|
191
|
+
/**
|
|
192
|
+
* Wake-on-LAN registry, returned by `registerSessionRoutes`. Held for its LIFETIME
|
|
193
|
+
* only — `drop()` on session cleanup, `stop()` on shutdown. Waking from here would
|
|
194
|
+
* re-wake a host on every timer tick (the invariant `remote-wake.ts` documents), so
|
|
195
|
+
* the wiring guard in `test/remote-wake.test.ts` pins that this file calls nothing
|
|
196
|
+
* but `drop`/`stop` on it.
|
|
197
|
+
*/
|
|
198
|
+
remoteWake = null;
|
|
168
199
|
sse;
|
|
169
200
|
store = getStore();
|
|
170
201
|
tabLayouts;
|
|
@@ -902,7 +933,9 @@ export class WebServer extends EventEmitter {
|
|
|
902
933
|
registerStatusTelemetryRoutes(this.app, ctx);
|
|
903
934
|
registerSystemRoutes(this.app, ctx);
|
|
904
935
|
registerCaseRoutes(this.app, ctx);
|
|
905
|
-
|
|
936
|
+
// The registry's lifetime is the server's: it drops per-session wake state on every
|
|
937
|
+
// cleanup path and resolves in-flight wakes on shutdown.
|
|
938
|
+
this.remoteWake = registerSessionRoutes(this.app, ctx);
|
|
906
939
|
registerRespawnRoutes(this.app, ctx);
|
|
907
940
|
registerRalphRoutes(this.app, ctx);
|
|
908
941
|
registerPlanRoutes(this.app, ctx);
|
|
@@ -1027,7 +1060,7 @@ export class WebServer extends EventEmitter {
|
|
|
1027
1060
|
return undefined;
|
|
1028
1061
|
}
|
|
1029
1062
|
try {
|
|
1030
|
-
return applyCustomModelInjection(entry, endpoint, saved.modelId, session.id)?.envOverrides;
|
|
1063
|
+
return applyCustomModelInjection(entry, endpoint, saved.modelId, session.id, endpoint.modelContextLengths?.[saved.modelId])?.envOverrides;
|
|
1031
1064
|
}
|
|
1032
1065
|
catch (err) {
|
|
1033
1066
|
console.warn('[WebServer] Failed to rebuild custom-model env on recovery:', err);
|
|
@@ -1112,6 +1145,9 @@ export class WebServer extends EventEmitter {
|
|
|
1112
1145
|
if (session) {
|
|
1113
1146
|
session.ralphTracker.stopWatchingFixPlan();
|
|
1114
1147
|
}
|
|
1148
|
+
// Custom Model Endpoint Profiles: drop this session's swap-displacement notify flag
|
|
1149
|
+
// (see _checkCustomModelSwapDisplacements below) so it can't linger in that Set forever.
|
|
1150
|
+
this._customModelDisplacedNotified.delete(sessionId);
|
|
1115
1151
|
// Kill all subagents spawned by this session (scoped to sessionId to avoid cross-session kills)
|
|
1116
1152
|
if (session && killMux) {
|
|
1117
1153
|
try {
|
|
@@ -1251,6 +1287,11 @@ export class WebServer extends EventEmitter {
|
|
|
1251
1287
|
sessionWaits.notifySignal(sessionId, 'exit');
|
|
1252
1288
|
sessionWaits.cancelAll(sessionId);
|
|
1253
1289
|
approvalInbox.resolveForSession(sessionId, 'session_ended');
|
|
1290
|
+
// Wake state goes with the session on EVERY cleanup path (delete routes, the cron
|
|
1291
|
+
// and admin paths, scheduled-run teardown, error paths) — that is why it lives here
|
|
1292
|
+
// rather than in the two delete routes, where it left an entry behind, including up
|
|
1293
|
+
// to 4 KB of the user's buffered keystrokes.
|
|
1294
|
+
this.remoteWake?.drop(sessionId);
|
|
1254
1295
|
this.broadcast(SseEvent.SessionDeleted, { id: sessionId });
|
|
1255
1296
|
}
|
|
1256
1297
|
async renderIndexHtml(soloSessionId) {
|
|
@@ -1368,6 +1409,20 @@ export class WebServer extends EventEmitter {
|
|
|
1368
1409
|
git: isGitAvailable(),
|
|
1369
1410
|
};
|
|
1370
1411
|
html = html.replace('</head>', `<script>window.__codemanCliAvailable=${JSON.stringify(available)};</script>\n</head>`);
|
|
1412
|
+
// Which run modes the Run-menu picker (docs/custom-model-endpoints-plan.md) may
|
|
1413
|
+
// generate an entry for: read generically off the registry's `capabilities`
|
|
1414
|
+
// (never an id list here) so a CLI whose customModelInjection lands later shows
|
|
1415
|
+
// up in the picker with no frontend change, and one that ships `unsupported`
|
|
1416
|
+
// (antigravity, and `shell`'s `kind !== 'agent'`) never does.
|
|
1417
|
+
const customModelClis = enabledClis()
|
|
1418
|
+
.filter((entry) => entry.kind === 'agent' && entry.capabilities.customModelInjection.kind !== 'unsupported')
|
|
1419
|
+
.map((entry) => ({ id: entry.id, label: entry.label }));
|
|
1420
|
+
// Unlike the boolean-only __codemanCliAvailable above, this payload carries
|
|
1421
|
+
// `label`, a string a user's own clis.json can set (CliEntry.label, up to 60
|
|
1422
|
+
// chars) — see escapeScriptJson's own doc comment for why that needs escaping
|
|
1423
|
+
// and __codemanCliAvailable's booleans never did.
|
|
1424
|
+
const customModelClisJson = escapeScriptJson(JSON.stringify(customModelClis));
|
|
1425
|
+
html = html.replace('</head>', `<script>window.__codemanCustomModelClis=${customModelClisJson};</script>\n</head>`);
|
|
1371
1426
|
}
|
|
1372
1427
|
if (!soloSessionId && process.env.CODEMAN_GESTURE === '1') {
|
|
1373
1428
|
html = html.replace('</head>', `<script>window.__codemanGestureAvailable=true;</script>\n</head>`);
|
|
@@ -2019,11 +2074,20 @@ export class WebServer extends EventEmitter {
|
|
|
2019
2074
|
'scheduled:',
|
|
2020
2075
|
'team:',
|
|
2021
2076
|
'case:',
|
|
2077
|
+
'remote:',
|
|
2078
|
+
'custom-model:',
|
|
2022
2079
|
];
|
|
2023
2080
|
if (SESSION_PREFIXES.some((p) => event.startsWith(p))) {
|
|
2024
2081
|
const d = (data ?? {});
|
|
2025
2082
|
const sessionId = d.sessionId ?? d.id ?? d.session?.id;
|
|
2026
2083
|
const owner = sessionId ? this.sessions.get(sessionId)?.owner : undefined;
|
|
2084
|
+
// `remote:hostWaking` / `remote:hostWakeFailed` for a create/attach wake have no
|
|
2085
|
+
// session yet (nothing exists until the host is up), so the registry names the
|
|
2086
|
+
// requesting user instead; the payload carries `hostId`/`label`, which non-admins
|
|
2087
|
+
// are not shown elsewhere. No session and no requester: admins only (fail closed).
|
|
2088
|
+
if (!sessionId && event.startsWith('remote:') && d.username) {
|
|
2089
|
+
return { username: d.username, sessionScoped: true };
|
|
2090
|
+
}
|
|
2027
2091
|
return { owner, sessionScoped: true };
|
|
2028
2092
|
}
|
|
2029
2093
|
// #20/#38: clipboard:write writes into the receiver's OS clipboard — route it to
|
|
@@ -2343,6 +2407,55 @@ export class WebServer extends EventEmitter {
|
|
|
2343
2407
|
description: 'Codex plan-usage refresh',
|
|
2344
2408
|
});
|
|
2345
2409
|
}
|
|
2410
|
+
// Custom Model Endpoint Profiles (docs/custom-model-endpoints-plan.md): keeps
|
|
2411
|
+
// each saved endpoint's discovered model list current with no manual
|
|
2412
|
+
// "Discover" click, so a model added on the server side (or one that drops
|
|
2413
|
+
// off) shows up in the Run-menu picker within one cycle. Best-effort per
|
|
2414
|
+
// endpoint (refreshAllCustomModelHosts skips one that's unreachable rather
|
|
2415
|
+
// than failing the sweep) and off in tests for the same reason the Codex
|
|
2416
|
+
// poll above is — no real network to hit, no server instance to keep alive.
|
|
2417
|
+
if (!this.testMode) {
|
|
2418
|
+
this.cleanup.setInterval(() => {
|
|
2419
|
+
// Reads the setting fresh on every tick, same reasoning as
|
|
2420
|
+
// readPlanUsageTelemetryEnabled() beside it: a live toggle takes effect
|
|
2421
|
+
// on the very next cycle, not just at server boot, and turning the
|
|
2422
|
+
// feature off actually stops the polling instead of only hiding the UI.
|
|
2423
|
+
void readCustomModelEndpointsEnabled()
|
|
2424
|
+
.then((enabled) => {
|
|
2425
|
+
if (!enabled)
|
|
2426
|
+
return;
|
|
2427
|
+
return refreshAllCustomModelHosts();
|
|
2428
|
+
})
|
|
2429
|
+
.catch((err) => {
|
|
2430
|
+
console.error('[custom-model] periodic re-discovery failed:', getErrorMessage(err));
|
|
2431
|
+
});
|
|
2432
|
+
}, CUSTOM_MODEL_REDISCOVER_INTERVAL_MS, { description: 'custom model endpoint re-discovery' });
|
|
2433
|
+
}
|
|
2434
|
+
// Custom Model Endpoint Profiles: the swap-conflict check on the apply/create routes
|
|
2435
|
+
// only ever runs at THAT session's own launch/apply moment — it cannot catch a LATER
|
|
2436
|
+
// eviction triggered by a different session's normal use, since llama-swap has no push
|
|
2437
|
+
// notification of its own and only swaps in response to a real inference request
|
|
2438
|
+
// (confirmed live: a session created while nothing else conflicted at that instant can
|
|
2439
|
+
// still get silently displaced afterward). This periodic sweep is what catches that
|
|
2440
|
+
// case after the fact and tells the displaced session's user, rather than leaving them
|
|
2441
|
+
// to discover it only when their next prompt behaves unexpectedly.
|
|
2442
|
+
if (!this.testMode) {
|
|
2443
|
+
this.cleanup.setInterval(() => {
|
|
2444
|
+
detectCustomModelSwapDisplacements(this.sessions.values(), this._customModelDisplacedNotified)
|
|
2445
|
+
.then((displacements) => {
|
|
2446
|
+
for (const displacement of displacements) {
|
|
2447
|
+
this.broadcast(SseEvent.CustomModelSwappedOut, displacement);
|
|
2448
|
+
}
|
|
2449
|
+
})
|
|
2450
|
+
.catch((err) => {
|
|
2451
|
+
console.error('[custom-model] swap-displacement check failed:', getErrorMessage(err));
|
|
2452
|
+
});
|
|
2453
|
+
// Same cadence, unrelated concern: close any /api/events tail (see
|
|
2454
|
+
// getLatestLlamaSwapLogLine) nothing has polled in a while, so a loading banner
|
|
2455
|
+
// that finished (or was abandoned) doesn't leave a connection open forever.
|
|
2456
|
+
pruneIdleLlamaSwapLogTails();
|
|
2457
|
+
}, CUSTOM_MODEL_SWAP_CHECK_INTERVAL_MS, { description: 'custom model swap-displacement check' });
|
|
2458
|
+
}
|
|
2346
2459
|
// Start scheduled runs cleanup timer
|
|
2347
2460
|
this.cleanup.setInterval(() => {
|
|
2348
2461
|
this.cleanupScheduledRuns();
|
|
@@ -2718,6 +2831,9 @@ export class WebServer extends EventEmitter {
|
|
|
2718
2831
|
console.log(`[Server] Found ${alive.length + discovered.length} alive mux session(s) from previous run`);
|
|
2719
2832
|
// For each alive mux session, create a Session object if it doesn't exist
|
|
2720
2833
|
const muxSessions = this.mux.getSessions();
|
|
2834
|
+
// Host-level config lives in remote-hosts.json, not in the persisted session
|
|
2835
|
+
// snapshot, so refresh the fields that only exist there (see the helper).
|
|
2836
|
+
const remoteHostsById = new Map((await readRemoteHosts(getDataDir())).map((host) => [host.id, host]));
|
|
2721
2837
|
for (const muxSession of muxSessions) {
|
|
2722
2838
|
if (!this.sessions.has(muxSession.sessionId)) {
|
|
2723
2839
|
// Restore session settings from state.json (single source of truth)
|
|
@@ -2792,7 +2908,9 @@ export class WebServer extends EventEmitter {
|
|
|
2792
2908
|
// respawn rebuilds a LOCAL command, breaking the pane and silently
|
|
2793
2909
|
// erasing `remote` from state.json on the next persist. mux-sessions.json
|
|
2794
2910
|
// round-trips MuxSession.remote; state.json carries SessionState.remote.
|
|
2795
|
-
remote
|
|
2911
|
+
// Host-level fields are refreshed from remote-hosts.json on top, or a
|
|
2912
|
+
// field added to the host config after launch would never arrive.
|
|
2913
|
+
remote: rehydrateRemoteHostFields(muxSession.remote ?? savedState?.remote, remoteHostsById),
|
|
2796
2914
|
// Docker metadata round-trips the same way (mux-sessions.json carries
|
|
2797
2915
|
// MuxSession.docker; state.json carries SessionState.docker), so recovery
|
|
2798
2916
|
// rebuilds the `docker exec` launch instead of a broken local command.
|
|
@@ -3137,6 +3255,9 @@ export class WebServer extends EventEmitter {
|
|
|
3137
3255
|
// port against the next start — the exact EADDRINUSE this feature already
|
|
3138
3256
|
// got wrong once.
|
|
3139
3257
|
void stopDeepSeekWeb();
|
|
3258
|
+
// Same teardown rule: the per-endpoint llama-swap log tails are otherwise closed
|
|
3259
|
+
// only by the periodic idle sweep, whose interval is disposed just below.
|
|
3260
|
+
closeAllLlamaSwapLogTails();
|
|
3140
3261
|
// Dispose all managed timers (intervals + resettable timeouts)
|
|
3141
3262
|
this.cleanup.dispose();
|
|
3142
3263
|
// Gracefully close all SSE connections and clear batching state
|
|
@@ -3146,6 +3267,10 @@ export class WebServer extends EventEmitter {
|
|
|
3146
3267
|
// response), so without this a 10-minute wait holds shutdown open.
|
|
3147
3268
|
sessionWaits.cancelEverything();
|
|
3148
3269
|
approvalInbox.stop();
|
|
3270
|
+
// Same reason as `cancelEverything` above: an in-flight wake is awaited by a request,
|
|
3271
|
+
// and `app.close()` (the last line of this method) does not abort in-flight requests —
|
|
3272
|
+
// so without this a restart during a wake waits out the readiness poll.
|
|
3273
|
+
this.remoteWake?.stop();
|
|
3149
3274
|
this.lastRecordedTokens.clear();
|
|
3150
3275
|
// Stop multiplexer and flush pending saves
|
|
3151
3276
|
this.mux.destroy();
|