diff --git a/README.md b/README.md index aff3c6c3..d390f06d 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Persistent admin and time travel
- When something breaks, admin mode gives you a stable control plane, and Git-backed history lets you roll back user or group changes without taking everyone down with you. + Enter Space opens the native workspace shell. Admin mode gives you the stable control plane, and Git-backed history lets you roll back user or group changes without taking everyone down with you. diff --git a/app/L0/_all/mod/_core/admin/views/agent/AGENTS.md b/app/L0/_all/mod/_core/admin/views/agent/AGENTS.md index cf174a05..213de3b1 100644 --- a/app/L0/_all/mod/_core/admin/views/agent/AGENTS.md +++ b/app/L0/_all/mod/_core/admin/views/agent/AGENTS.md @@ -85,7 +85,7 @@ Current behavior: - the admin HuggingFace panel should let users either enter a compatible repo id directly or pick from the shared saved-model list, then load or unload that selection directly from the modal while reusing the same progress block and current-model status area as the routed sidebar - the admin local-provider panel should show the selected model separately from the currently loaded model, so an unloaded but configured selection is visible immediately instead of looking stuck on `None loaded` - when no Hugging Face model is selected and the shared saved-model list has entries, the admin local-provider panel should preselect the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded -- when no Hugging Face model is selected, no model is loaded, and the shared saved-model list is empty, the admin local-provider panel should prefill the model field with the same default used by the routed testing page: `onnx-community/gemma-4-E4B-it-ONNX` +- when no Hugging Face model is selected, no model is loaded, and the shared saved-model list is empty, the admin local-provider panel should prefill the model field with the same default used by the routed testing page: `onnx-community/Qwen3-0.6B-ONNX` - admin-local provider inputs mounted through the shared sidebar components should write back through explicit `store.js` setter methods instead of depending on implicit nested `x-model` mutation across component boundaries - discarding a HuggingFace repo from the routed testing harness removes it from the shared browser-side saved-model list too, so it disappears from the admin saved-model shortcut selector until it is loaded again - admin should subscribe to `_core/huggingface/manager.js` directly, so the modal and send flow read the same live worker state, saved-model options, loading, and streaming behavior as the routed Hugging Face surface within the current browser context diff --git a/app/L0/_all/mod/_core/admin/views/agent/store.js b/app/L0/_all/mod/_core/admin/views/agent/store.js index 4aacf7b4..65b97b1a 100644 --- a/app/L0/_all/mod/_core/admin/views/agent/store.js +++ b/app/L0/_all/mod/_core/admin/views/agent/store.js @@ -803,17 +803,6 @@ const model = { return false; } - const preferredSavedModel = huggingfaceManager.refreshPreferredSavedModelSelection(); - - if (preferredSavedModel?.modelId && preferredSavedModel?.dtype) { - this.settingsDraft = { - ...this.settingsDraft, - huggingfaceDtype: preferredSavedModel.dtype, - huggingfaceModel: preferredSavedModel.modelId - }; - return true; - } - const snapshot = huggingfaceManager.getSnapshot(); const hasSavedModels = Array.isArray(snapshot.savedModels) && snapshot.savedModels.length > 0; const activeModelId = normalizeHuggingFaceModelInput(snapshot.activeModelId || ""); @@ -1633,21 +1622,31 @@ const model = { }, openSettingsDialog() { + const provider = config.normalizeAdminChatLlmProvider(this.settings.provider); + this.settingsDraft = { ...this.settings, promptBudgetRatios: clonePromptBudgetRatios(this.settings.promptBudgetRatios) }; this.syncHuggingFaceFromManager(); - this.prefillSettingsDraftDefaultHuggingFaceModel(); - if (!String(this.settingsDraft.huggingfaceDtype || "").trim()) { - this.settingsDraft.huggingfaceDtype = DTYPE_OPTIONS[0]?.value || config.DEFAULT_ADMIN_CHAT_SETTINGS.huggingfaceDtype; + if (provider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL) { + this.prefillSettingsDraftDefaultHuggingFaceModel(); + + if (!String(this.settingsDraft.huggingfaceDtype || "").trim()) { + this.settingsDraft.huggingfaceDtype = DTYPE_OPTIONS[0]?.value || config.DEFAULT_ADMIN_CHAT_SETTINGS.huggingfaceDtype; + } + } else { + this.settingsDraft.huggingfaceModel = ""; + this.settingsDraft.huggingfaceDtype = ""; } - void this.warmSettingsDraftLocalProvider() - .catch((error) => { - this.reportError("warming the local-provider settings draft", error); - }); + if (provider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL) { + void this.warmSettingsDraftLocalProvider() + .catch((error) => { + this.reportError("warming the local-provider settings draft", error); + }); + } openDialog(this.refs.settingsDialog); }, @@ -1656,9 +1655,17 @@ const model = { }, setSettingsProvider(provider) { + const nextProvider = config.normalizeAdminChatLlmProvider(provider); + this.settingsDraft = { ...this.settingsDraft, - provider: config.normalizeAdminChatLlmProvider(provider) + huggingfaceDtype: nextProvider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL + ? this.settingsDraft.huggingfaceDtype + : "", + huggingfaceModel: nextProvider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL + ? this.settingsDraft.huggingfaceModel + : "", + provider: nextProvider }; if (this.isSettingsDraftUsingLocalProvider) { @@ -1877,8 +1884,12 @@ const model = { this.settings = { apiEndpoint: (this.settingsDraft.apiEndpoint || "").trim(), apiKey: (this.settingsDraft.apiKey || "").trim(), - huggingfaceDtype: (this.settingsDraft.huggingfaceDtype || "").trim(), - huggingfaceModel: normalizeHuggingFaceModelInput(this.settingsDraft.huggingfaceModel || ""), + huggingfaceDtype: provider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL + ? (this.settingsDraft.huggingfaceDtype || "").trim() + : "", + huggingfaceModel: provider === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL + ? normalizeHuggingFaceModelInput(this.settingsDraft.huggingfaceModel || "") + : "", localProvider, maxTokens, model: (this.settingsDraft.model || "").trim(), diff --git a/app/L0/_all/mod/_core/agent-chat/AGENTS.md b/app/L0/_all/mod/_core/agent-chat/AGENTS.md index e2c31dab..b5c7df12 100644 --- a/app/L0/_all/mod/_core/agent-chat/AGENTS.md +++ b/app/L0/_all/mod/_core/agent-chat/AGENTS.md @@ -13,6 +13,7 @@ Documentation is top priority for this module. After any change under `_core/age This module owns: - `assistant-message-evaluation.js`: shared assistant-message normalization, exact-repeat detection, severity-based loop warning construction, and safe prepending of synthetic transcript logs ahead of real execution console output +- `visual-data.js`: shared image visual-data normalization, serialization, token estimation, and transport shaping for first-party agent surfaces; its framework dependency stays relative so pure consumers remain directly importable in browser and Node verification runtimes - `ext/js/_core/onscreen_agent/store.js/evaluateOnscreenAssistantMessage/end/*.js`: hook implementations for overlay assistant-message evaluation - `ext/js/_core/admin/views/agent/store.js/evaluateAdminAssistantMessage/end/*.js`: hook implementations for admin assistant-message evaluation @@ -24,6 +25,7 @@ Current shared helper contract: - assistant-message repeat matching should normalize line endings, trim trailing per-line whitespace, and trim outer whitespace before comparing exact assistant-message bodies - synthetic transcript warnings should be emitted only when the same normalized assistant message has already appeared earlier in the settled assistant history for that same surface +- vision transport shaping must convert user visual data into provider-facing image content parts and must remove internal `visualData` and `tokenCount` metadata from outbound messages - severity must escalate as `info` on the 2nd exact send, `warn` on the 3rd exact send, and `error` on the 4th exact send onward - the warning text should stay short, direct, and framed as loop pressure visible through the normal execution transcript channel - prepending synthetic transcript logs must not rewrite or trim the real execution console entries that already exist on the execution result @@ -35,6 +37,7 @@ Current shared helper contract: - keep framework-generic runtime helpers in `_core/framework/`; keep chat-feature policy here - keep surface-specific store behavior in the owning chat modules and reuse this module only for logic that is intentionally shared across agent surfaces +- keep shared pure helper imports browser- and Node-resolvable; use relative module specifiers when the helper is covered by direct Node imports - when repeat-detection thresholds, wording, or transcript insertion semantics change, update this file, the consuming chat docs, and the supplemental agent-runtime docs in the same session ## Verification diff --git a/app/L0/_all/mod/_core/agent-chat/visual-data.js b/app/L0/_all/mod/_core/agent-chat/visual-data.js index d21bb65e..840c03f4 100644 --- a/app/L0/_all/mod/_core/agent-chat/visual-data.js +++ b/app/L0/_all/mod/_core/agent-chat/visual-data.js @@ -1,4 +1,4 @@ -import { countTextTokens } from "/mod/_core/framework/js/token-count.js"; +import { countTextTokens } from "../framework/js/token-count.js"; const DATA_URL_PATTERN = /^data:([^;,]+)?((?:;[^,]+)*),(.*)$/isu; const DEFAULT_IMAGE_MEDIA_TYPE = "image/png"; @@ -677,8 +677,12 @@ export function prepareChatMessagesForVisionTransport(messages = [], options = { return null; } + const transportMessage = { ...message }; + delete transportMessage.tokenCount; + delete transportMessage.visualData; + return { - ...message, + ...transportMessage, content: buildVisionContentParts(message, options) }; }) diff --git a/app/L0/_all/mod/_core/documentation/docs/agent/onscreen-agent-runtime.md b/app/L0/_all/mod/_core/documentation/docs/agent/onscreen-agent-runtime.md index 30d18b95..b01f7d5c 100644 --- a/app/L0/_all/mod/_core/documentation/docs/agent/onscreen-agent-runtime.md +++ b/app/L0/_all/mod/_core/documentation/docs/agent/onscreen-agent-runtime.md @@ -110,7 +110,7 @@ The settings and prompt-history dialogs reuse the shared `_core/visual/forms/dia Caught overlay runtime errors are logged through `console.error` and shown through the shared toast stack from `_core/visual/chrome/toast.js`. The composer placeholder still belongs to ready-state and lightweight status guidance, so raw exception text should not be pushed into the textarea placeholder. Overlay execution transcripts now use the shared YAML-first formatter for both console logs and returned values, emitting block headers such as `log↓`, `warn↓`, `error↓`, and `result↓` so structured telemetry stays complete across the thread and execution cards. Queued follow-up submissions wait behind any just-finished assistant reply that contains `_____javascript`; the runtime must execute the block and append the `execution-output` turn before sending the queued draft, so the next model request sees the execution result in history. Immediately before those execution results are serialized back into the `execution-output` follow-up turn, the overlay also runs the shared assistant-message evaluation seam; the current first-party hook from `_core/agent-chat` prepends synthetic loop warnings when the exact same assistant message reappears, using `info` on the 2nd send, `warn` on the 3rd send, and `error` on the 4th send onward. -The settings dialog now has two provider tabs named `API` and `Local`. `API` keeps the OpenAI-compatible endpoint, model, and key fields. `Local` mounts the shared Hugging Face config sidebar in onscreen mode, so the overlay reads the same saved-model list and live WebGPU worker state as the routed Local LLM page and the admin chat. Opening the Local tab should refresh saved-model shortcuts without booting the worker; saving local settings persists the selected repo id and dtype, then starts background model preparation. When no local model is selected and saved models exist, the Local panel preselects the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded. When no local model is selected, no local model is loaded, and the shared saved-model list is empty, the Local panel prefills the Hugging Face model field with the same testing-page default: `onnx-community/gemma-4-E4B-it-ONNX`. +The settings dialog now has two provider tabs named `API` and `Local`. `API` keeps the OpenAI-compatible endpoint, model, and key fields. `Local` mounts the shared Hugging Face config sidebar in onscreen mode, so the overlay reads the same saved-model list and live WebGPU worker state as the routed Local LLM page and the admin chat. Opening the Local tab should refresh saved-model shortcuts without booting the worker; saving local settings persists the selected repo id and dtype, then starts background model preparation. When no local model is selected and saved models exist, the Local panel preselects the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded. When no local model is selected, no local model is loaded, and the shared saved-model list is empty, the Local panel prefills the Hugging Face model field with the same testing-page default: `onnx-community/Qwen3-0.6B-ONNX`. The API-key composer blocker applies only to the default API-provider configuration with no API key, where the composer shows a centered `Set LLM API key` action over the disabled textarea. Local Hugging Face mode can send without an API key and falls back to loading the selected local model on the first message if background preparation has not finished. diff --git a/app/L0/_all/mod/_core/documentation/docs/app/admin-agent-runtime.md b/app/L0/_all/mod/_core/documentation/docs/app/admin-agent-runtime.md index 458c5902..6637643a 100644 --- a/app/L0/_all/mod/_core/documentation/docs/app/admin-agent-runtime.md +++ b/app/L0/_all/mod/_core/documentation/docs/app/admin-agent-runtime.md @@ -64,7 +64,7 @@ The admin settings modal now starts with a provider switch: Below those provider-specific sections, the shared settings area also exposes `max_tokens`, prompt-budget ratios for `system`, `history`, and `transient`, plus the separate single-history-message ratio used by the shared trimming path. Those values are persisted in `prompt_budget_ratios` and feed the same prompt-budget builder used by the onscreen agent: prepared entries and prompt items reuse cached token counts, single live history messages are capped first, contributor-level trims must each be at least `250` tokens, and `system` or `transient` falls back to one combined section-body trim when smaller contributor cuts would otherwise be required. -When no local model is selected and saved models exist, the admin local panel preselects the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded. When no local model is selected, no local model is loaded, and the shared saved-model list is empty, the admin local panel prefills the Hugging Face model field with the same testing-page default: `onnx-community/gemma-4-E4B-it-ONNX`. +When no local model is selected and saved models exist, the admin local panel preselects the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded. When no local model is selected, no local model is loaded, and the shared saved-model list is empty, the admin local panel prefills the Hugging Face model field with the same testing-page default: `onnx-community/Qwen3-0.6B-ONNX`. The stored config keeps both API settings and the selected local provider state: diff --git a/app/L0/_all/mod/_core/documentation/docs/app/huggingface-browser-runtime.md b/app/L0/_all/mod/_core/documentation/docs/app/huggingface-browser-runtime.md index ae3bafec..7c62f31f 100644 --- a/app/L0/_all/mod/_core/documentation/docs/app/huggingface-browser-runtime.md +++ b/app/L0/_all/mod/_core/documentation/docs/app/huggingface-browser-runtime.md @@ -44,7 +44,7 @@ The page owns: - a simple testing chat with system prompt, user messages, streamed assistant replies, stop, and clear-chat - compact response metrics inline under each assistant reply -When admin or onscreen chat local settings have no selected model and the browser has saved Hugging Face models, the shared sidebar preselects the browser-wide last successfully loaded saved model from local storage. If that entry was discarded, it falls back to the first saved model. When the browser has no saved local Hugging Face models and no persisted auto-reload target, the routed testing-page model input prefills `onnx-community/gemma-4-E4B-it-ONNX` as the empty-state suggestion; admin and onscreen chat local settings reuse that same default only when there is no preferred saved-model selection. The default generation cap is `16384` max new tokens unless the user changes it. +When admin or onscreen chat local settings have no selected model and the browser has saved Hugging Face models, the shared sidebar preselects the browser-wide last successfully loaded saved model from local storage. If that entry was discarded, it falls back to the first saved model. When the browser has no saved local Hugging Face models and no persisted auto-reload target, the routed testing-page model input prefills `onnx-community/Qwen3-0.6B-ONNX` as the empty-state suggestion; admin and onscreen chat local settings reuse that same default only when there is no preferred saved-model selection. The default generation cap is `16384` max new tokens unless the user changes it. This is not a general agent surface. It does not expose tool execution, queueing, attachments, persisted conversations, or backend orchestration. diff --git a/app/L0/_all/mod/_core/framework/AGENTS.md b/app/L0/_all/mod/_core/framework/AGENTS.md index ec12e029..24325c9d 100644 --- a/app/L0/_all/mod/_core/framework/AGENTS.md +++ b/app/L0/_all/mod/_core/framework/AGENTS.md @@ -31,6 +31,7 @@ This module owns: - Alpine directives and magic helpers registered during bootstrap, including delayed-target `x-inject` - shared browser API helpers in `js/api-client.js`, `js/api.js`, `js/fetch-proxy.js`, `js/download.js`, and `js/proxy-url.js` - small shared parsing and utility helpers such as markdown frontmatter, the browser YAML wrapper, and token counting +- `js/token-count.js`: shared token counting with relative vendor imports so direct Node tests and browser module loading execute the same implementation - shared framework CSS and icon font assets under `css/`, including non-visual helper-tag defaults such as hidden `x-context` elements ## Local Contracts @@ -141,6 +142,7 @@ Rules: ### Local Work Rules - keep this module focused on platform concerns, not feature logic +- keep pure framework utilities that are directly imported by Node tests on relative internal imports; browser-absolute `/mod/...` specifiers remain appropriate for browser-only runtime entry points - add shared runtime helpers here only when multiple modules genuinely need them - prefer explicit small runtime namespaces over loose globals - if a contract is used by only one module, keep it in that module instead of promoting it here too early diff --git a/app/L0/_all/mod/_core/framework/js/token-count.js b/app/L0/_all/mod/_core/framework/js/token-count.js index 8e1c1c08..e7733f6e 100644 --- a/app/L0/_all/mod/_core/framework/js/token-count.js +++ b/app/L0/_all/mod/_core/framework/js/token-count.js @@ -1,5 +1,5 @@ -import { Tiktoken } from "/mod/_core/framework/js/vendor/js-tiktoken-lite.js"; -import o200kBase from "/mod/_core/framework/js/vendor/js-tiktoken-o200k_base.js"; +import { Tiktoken } from "./vendor/js-tiktoken-lite.js"; +import o200kBase from "./vendor/js-tiktoken-o200k_base.js"; let tokenizer = null; diff --git a/app/L0/_all/mod/_core/huggingface/AGENTS.md b/app/L0/_all/mod/_core/huggingface/AGENTS.md index ae2513a5..c7b51d7e 100644 --- a/app/L0/_all/mod/_core/huggingface/AGENTS.md +++ b/app/L0/_all/mod/_core/huggingface/AGENTS.md @@ -42,7 +42,7 @@ Current route contract: - `manager.js` exposes startup explicitly through `isWorkerBooting`; routed, admin, and onscreen consumers should treat an unbooted idle manager as `Idle`, not as `Starting`, and should reserve `Starting` for an actual in-flight worker boot - `manager.js` snapshots and subscription payloads must stay plain-data and clone-safe; do not leak raw browser host objects or rely on generic `structuredClone(...)` fallbacks that can fail on reactive proxies - when the admin or onscreen local-provider draft has no selected model and saved Hugging Face models exist in browser storage, the sidebar should preselect the last successfully loaded saved model from browser-wide `localStorage`; if that stored model is no longer in the saved list, it should fall back to the first saved entry -- when the browser has no saved models, no active model, and no persisted auto-reload target, the routed testing-page model input should prefill `onnx-community/gemma-4-E4B-it-ONNX` as the empty-state suggestion; admin and onscreen chat sidebars should reuse that same empty-state default only when their selected local model is blank and there is no preferred saved-model selection +- when the browser has no saved models, no active model, and no persisted auto-reload target, the routed testing-page model input should prefill `onnx-community/Qwen3-0.6B-ONNX` as the empty-state suggestion; admin and onscreen chat sidebars should reuse that same empty-state default only when their selected local model is blank and there is no preferred saved-model selection - the sidebar should surface the currently loaded model first, inside a slightly more prominent rounded panel with a larger model label, a compact right-aligned state badge, and an unload control beside the model name - while a model is loading, that action switches to `Stop`; stopping a Hugging Face load or unload resets the shared singleton-managed worker and boots a fresh one instead of leaving stale partial state behind - the load progress area should show a debounced aggregate download status below the bar, with total transferred bytes appended such as `Downloading model files (412 MB / 1.8 GB)` instead of rapidly alternating per-file names diff --git a/app/L0/_all/mod/_core/huggingface/config-sidebar.html b/app/L0/_all/mod/_core/huggingface/config-sidebar.html index 34cb12fd..90c5f80d 100644 --- a/app/L0/_all/mod/_core/huggingface/config-sidebar.html +++ b/app/L0/_all/mod/_core/huggingface/config-sidebar.html @@ -60,7 +60,7 @@

Models

:value="$store.huggingface.modelInput" @input="$store.huggingface.setModelInput($event.target.value)" :disabled="$store.huggingface.isGenerating" - placeholder="onnx-community/gemma-4-E4B-it-ONNX or https://huggingface.co/..." + placeholder="onnx-community/Qwen3-0.6B-ONNX or https://huggingface.co/..." /> @@ -228,7 +228,7 @@

Advanced

type="text" :value="$store.adminAgent.settingsDraft.huggingfaceModel" @input="$store.adminAgent.handleSettingsHuggingFaceModelInput($event.target.value)" - placeholder="onnx-community/gemma-4-E4B-it-ONNX" + placeholder="onnx-community/Qwen3-0.6B-ONNX" /> @@ -342,7 +342,7 @@

Advanced

type="text" :value="$store.onscreenAgent.settingsDraft.huggingfaceModel" @input="$store.onscreenAgent.handleSettingsHuggingFaceModelInput($event.target.value)" - placeholder="onnx-community/gemma-4-E4B-it-ONNX" + placeholder="onnx-community/Qwen3-0.6B-ONNX" /> diff --git a/app/L0/_all/mod/_core/huggingface/helpers.js b/app/L0/_all/mod/_core/huggingface/helpers.js index 18060e7e..6c3062cf 100644 --- a/app/L0/_all/mod/_core/huggingface/helpers.js +++ b/app/L0/_all/mod/_core/huggingface/helpers.js @@ -1,6 +1,7 @@ export const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant."; export const DEFAULT_DTYPE = "q4"; -export const DEFAULT_MODEL_INPUT = "onnx-community/gemma-4-E4B-it-ONNX"; +export const LEGACY_DEFAULT_MODEL_INPUT = "onnx-community/gemma-4-E4B-it-ONNX"; +export const DEFAULT_MODEL_INPUT = "onnx-community/Qwen3-0.6B-ONNX"; export const DEFAULT_MAX_NEW_TOKENS = 16384; export const COMPATIBLE_MODELS_URL = "https://huggingface.co/onnx-community/models"; export const HUGGINGFACE_SAVED_MODELS_STORAGE_KEY = "space.huggingface.saved-models"; @@ -209,9 +210,23 @@ export function readSavedModelEntries() { return []; } - return parsedValue + const nextEntries = parsedValue .map((entry) => createSavedModelEntry(entry)) + .filter((entry) => entry?.modelId !== LEGACY_DEFAULT_MODEL_INPUT) .filter(Boolean); + + if (nextEntries.length !== parsedValue.length) { + try { + globalThis.localStorage?.setItem( + HUGGINGFACE_SAVED_MODELS_STORAGE_KEY, + JSON.stringify(nextEntries) + ); + } catch { + // Ignore storage write failures and still return the sanitized list. + } + } + + return nextEntries; } catch { return []; } diff --git a/app/L0/_all/mod/_core/huggingface/manager.js b/app/L0/_all/mod/_core/huggingface/manager.js index 9862bb4e..90415963 100644 --- a/app/L0/_all/mod/_core/huggingface/manager.js +++ b/app/L0/_all/mod/_core/huggingface/manager.js @@ -4,6 +4,7 @@ import { DEFAULT_DTYPE, DEFAULT_MAX_NEW_TOKENS, DEFAULT_MODEL_INPUT, + LEGACY_DEFAULT_MODEL_INPUT, discardCachedModelEntries, describeModelSelection, DTYPE_OPTIONS, @@ -273,6 +274,11 @@ function readPersistedModelSelection() { return null; } + if (modelId === LEGACY_DEFAULT_MODEL_INPUT) { + globalThis.localStorage?.removeItem(PERSISTED_MODEL_STORAGE_KEY); + return null; + } + return { dtype: String(parsedValue.dtype || DEFAULT_DTYPE).trim() || DEFAULT_DTYPE, maxNewTokens: normalizeMaxNewTokens(parsedValue.maxNewTokens), diff --git a/app/L0/_all/mod/_core/onscreen_agent/AGENTS.md b/app/L0/_all/mod/_core/onscreen_agent/AGENTS.md index a91a40c7..9ed42810 100644 --- a/app/L0/_all/mod/_core/onscreen_agent/AGENTS.md +++ b/app/L0/_all/mod/_core/onscreen_agent/AGENTS.md @@ -260,7 +260,7 @@ Current overlay behavior: - the settings modal keeps a provider switch with exactly two tabs named `API` and `Local`; API settings show endpoint, model, and API key fields, while local settings mount the shared `_core/huggingface/config-sidebar.html` component in `onscreen` mode - local provider settings are limited to the shared Hugging Face browser runtime for now; the overlay subscribes to `_core/huggingface/manager.js`, reads the same saved-model list and live worker state as the routed Local LLM page, and should not boot the worker just because the modal opened - when no Hugging Face model is selected and the shared saved-model list has entries, the overlay local-provider panel should preselect the browser-wide last successfully loaded saved model from `_core/huggingface/manager.js`, falling back to the first saved entry if that last-used entry was discarded -- when no Hugging Face model is selected, no model is loaded, and the shared saved-model list is empty, the overlay local-provider panel should prefill the model field with the same default used by the routed testing page: `onnx-community/gemma-4-E4B-it-ONNX` +- when no Hugging Face model is selected, no model is loaded, and the shared saved-model list is empty, the overlay local-provider panel should prefill the model field with the same default used by the routed testing page: `onnx-community/Qwen3-0.6B-ONNX` - saving local settings must persist the selected Hugging Face repo id and dtype, then start background model preparation; the first local send remains the fallback load trigger if that preparation has not finished - local-provider sends should use the same prompt assembly path as API sends, including the full firmware prompt, prompt-include sections, skill catalog, auto-loaded skill context, custom instructions, history, and transient context; the routed `_core/huggingface` testing page remains the first-party plain system-prompt-only local-LLM surface - local-provider sends should reuse the same final transport message payload that the API path sends upstream; `requestBody.messages` is the local runtime input, while the richer prepared prompt entries remain the prompt-history inspection surface @@ -298,6 +298,7 @@ Current overlay behavior: - keep `_core/onscreen_agent` generic: do not add task-specific execution validators, helper workflows, or module-owned prompt rules here when the owning module can supply them through `ext/js/` or skills - keep onscreen skill discovery and runtime behavior separate from the admin agent even when copying skill content for starter coverage - keep overlay-local Hugging Face glue limited to snapshot shaping, settings state, and calls into the shared `_core/huggingface/manager.js`; do not fork a second Hugging Face worker or import admin-agent local-provider helpers +- keep directly Node-tested helpers such as `attachments.js` on relative imports for shared pure modules so the same source resolves in both the browser module tree and direct verification imports - keep `ext/skills/development/` aligned with the current frontend and read-only backend contracts so the onscreen agent's development guidance does not drift - keep prompt-surface strings lean: prefer `id|name|description` rows, short block labels, and body-only auto-loaded skill text over verbose wrappers - if behavior becomes meaningfully shared with the admin agent, promote it into `_core/framework` or `_core/visual` instead of creating cross-surface dependencies diff --git a/app/L0/_all/mod/_core/onscreen_agent/attachments.js b/app/L0/_all/mod/_core/onscreen_agent/attachments.js index dbf617d7..6af6fc47 100644 --- a/app/L0/_all/mod/_core/onscreen_agent/attachments.js +++ b/app/L0/_all/mod/_core/onscreen_agent/attachments.js @@ -2,7 +2,7 @@ import { formatVisualDataDimensions, normalizeVisualDataList, serializeVisualDataList -} from "/mod/_core/agent-chat/visual-data.js"; +} from "../agent-chat/visual-data.js"; const ATTACHMENT_ID_PREFIX = "attachment"; const DEFAULT_ATTACHMENT_TYPE = "application/octet-stream"; diff --git a/app/L0/_all/mod/_core/onscreen_agent/store.js b/app/L0/_all/mod/_core/onscreen_agent/store.js index ae4fba33..d62800b0 100644 --- a/app/L0/_all/mod/_core/onscreen_agent/store.js +++ b/app/L0/_all/mod/_core/onscreen_agent/store.js @@ -2945,17 +2945,6 @@ const model = { return false; } - const preferredSavedModel = huggingfaceManager.refreshPreferredSavedModelSelection(); - - if (preferredSavedModel?.modelId && preferredSavedModel?.dtype) { - this.settingsDraft = { - ...this.settingsDraft, - huggingfaceDtype: preferredSavedModel.dtype, - huggingfaceModel: preferredSavedModel.modelId - }; - return true; - } - const snapshot = huggingfaceManager.getSnapshot(); const hasSavedModels = Array.isArray(snapshot.savedModels) && snapshot.savedModels.length > 0; const activeModelId = normalizeHuggingFaceModelInput(snapshot.activeModelId || ""); @@ -4394,24 +4383,34 @@ const model = { }, openSettingsDialog() { + const provider = config.normalizeOnscreenAgentLlmProvider(this.settings.provider); + this.settingsDraft = { ...this.settings, promptBudgetRatios: clonePromptBudgetRatios(this.settings.promptBudgetRatios) }; this.syncHuggingFaceFromManager(); - this.prefillSettingsDraftDefaultHuggingFaceModel(); - if (!String(this.settingsDraft.huggingfaceDtype || "").trim()) { - this.settingsDraft.huggingfaceDtype = - DTYPE_OPTIONS[0]?.value || config.DEFAULT_ONSCREEN_AGENT_SETTINGS.huggingfaceDtype; + if (provider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL) { + this.prefillSettingsDraftDefaultHuggingFaceModel(); + + if (!String(this.settingsDraft.huggingfaceDtype || "").trim()) { + this.settingsDraft.huggingfaceDtype = + DTYPE_OPTIONS[0]?.value || config.DEFAULT_ONSCREEN_AGENT_SETTINGS.huggingfaceDtype; + } + } else { + this.settingsDraft.huggingfaceModel = ""; + this.settingsDraft.huggingfaceDtype = ""; } this.systemPromptDraft = this.systemPrompt; - void this.warmSettingsDraftLocalProvider().catch((error) => { - this.reportError("warming the local-provider settings draft", error, { - preserveStatus: true + if (provider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL) { + void this.warmSettingsDraftLocalProvider().catch((error) => { + this.reportError("warming the local-provider settings draft", error, { + preserveStatus: true + }); }); - }); + } openDialog(resolveDialogRef(this.refs, "settingsDialog", SETTINGS_DIALOG_ELEMENT_ID)); }, @@ -4420,9 +4419,17 @@ const model = { }, setSettingsProvider(provider) { + const nextProvider = config.normalizeOnscreenAgentLlmProvider(provider); + this.settingsDraft = { ...this.settingsDraft, - provider: config.normalizeOnscreenAgentLlmProvider(provider) + huggingfaceDtype: nextProvider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL + ? this.settingsDraft.huggingfaceDtype + : "", + huggingfaceModel: nextProvider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL + ? this.settingsDraft.huggingfaceModel + : "", + provider: nextProvider }; if (this.isSettingsDraftUsingLocalProvider) { @@ -4604,8 +4611,12 @@ const model = { this.settings = { apiEndpoint: (this.settingsDraft.apiEndpoint || "").trim(), apiKey: (this.settingsDraft.apiKey || "").trim(), - huggingfaceDtype: (this.settingsDraft.huggingfaceDtype || "").trim(), - huggingfaceModel: normalizeHuggingFaceModelInput(this.settingsDraft.huggingfaceModel || ""), + huggingfaceDtype: provider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL + ? (this.settingsDraft.huggingfaceDtype || "").trim() + : "", + huggingfaceModel: provider === config.ONSCREEN_AGENT_LLM_PROVIDER.LOCAL + ? normalizeHuggingFaceModelInput(this.settingsDraft.huggingfaceModel || "") + : "", localProvider, maxTokens, model: (this.settingsDraft.model || "").trim(), diff --git a/app/share/novamaster-scout.html b/app/share/novamaster-scout.html new file mode 100644 index 00000000..ac0f5a3f --- /dev/null +++ b/app/share/novamaster-scout.html @@ -0,0 +1,227 @@ + + + + + +NovaMaster Scout + + + +
+
+

🛰️ NovaMaster Scout

+ Real-time stack health monitor +
+
● checking
+
+
+ + +
+
+
+ ⚠️ Cross-origin (CORS) may block health checks from browser. Serve via python3 -m http.server 8080 or open via Space Agent for best results. +
+
+
0 online
+
0 offline
+
0 CORS
+
+ + + + + \ No newline at end of file diff --git a/server/pages/AGENTS.md b/server/pages/AGENTS.md index 875d65d4..eabc8af1 100644 --- a/server/pages/AGENTS.md +++ b/server/pages/AGENTS.md @@ -112,7 +112,7 @@ Current public shell assets: - must stay safe even when routed customware is broken - must not depend on authenticated `/mod/...` assets - is served for launcher-eligible sessions; in multi-user mode, unauthenticated requests are redirected to `/login` before this shell loads -- owns the firmware-backed launcher UI that links to `/` and `/admin`, labeled as Enter Space and Admin Mode, and when the Electron preload bridge reports a packaged desktop runtime with updater support it also runs a fresh background update check on each shell load unless an install is already downloading or ready to restart, reveals an update button below `Admin Mode` only after a newer bundle is available or ready to install, keeps all normal update status inside that button label with no second text line or subtitle underneath, uses the downloaded-state label `Restart and update`, opens a login-styled confirmation modal before restart-to-install with `Okay, restart` and `Back` actions plus copy explaining that the bundled app will quit and update in the background, fades the launcher shell to black only after the user confirms that modal, stays visually quiet when the bundled app is already current, and only replaces the button with a `Could not check updates` disclosure when the update check or download fails, rendering the update button version with a `v` prefix while still collapsing redundant updater versions such as `0.44.0` to the two-segment display form `v0.44` +- owns the firmware-backed launcher UI that links to `/` and `/admin`, labeled as Enter Space and Admin Mode, and treats Enter Space as the native workspace shell while Admin Mode is the stable control plane for recovery, admin, and rollback; when the Electron preload bridge reports a packaged desktop runtime with updater support it also runs a fresh background update check on each shell load unless an install is already downloading or ready to restart, reveals an update button below `Admin Mode` only after a newer bundle is available or ready to install, keeps all normal update status inside that button label with no second text line or subtitle underneath, uses the downloaded-state label `Restart and update`, opens a login-styled confirmation modal before restart-to-install with `Okay, restart` and `Back` actions plus copy explaining that the bundled app will quit and update in the background, fades the launcher shell to black only after the user confirms that modal, stays visually quiet when the bundled app is already current, and only replaces the button with a `Could not check updates` disclosure when the update check or download fails, rendering the update button version with a `v` prefix while still collapsing redundant updater versions such as `0.44.0` to the two-segment display form `v0.44` - declares that same shared product-level Open Graph and Twitter social-preview card so launcher-route shares use the same public Space Agent banner and description - declares the shared Space Agent transparent-helmet favicon set, including ICO fallback, PNG browser and install icons, Apple touch icon, and the `Enter Space | Space Agent` document title - runs the shared public-shell browser compatibility gate from `server/pages/res/browser-compat.js` before launcher logic starts, and renders the same blocking message contract as `/login` when the browser is missing required runtime features for the later app shell diff --git a/server/pages/enter.html b/server/pages/enter.html index 38dafd72..58f58877 100644 --- a/server/pages/enter.html +++ b/server/pages/enter.html @@ -427,6 +427,14 @@ word-break: break-word; } + .launcher-mode-note { + margin: 0.15rem 0 0; + color: var(--color-text-tertiary); + font-size: 0.84rem; + line-height: 1.45; + text-align: center; + } + .run-modal { position: fixed; inset: 0; @@ -768,6 +776,9 @@

Space Agent


           
         
+        

+ Enter Space opens the native workspace. Admin Mode opens the stable control plane. +

{ + const sourceMessage = { + content: "Describe this image.", + role: "user", + tokenCount: 999, + visualData: [VISUAL_DATA] + }; + const messages = prepareChatMessagesForVisionTransport([sourceMessage], { + model: "gpt-5.4", + supportsVision: true + }); + + assert.deepEqual(messages, [ + { + content: [ + { + text: "Describe this image.", + type: "text" + }, + { + image_url: { + detail: "high", + url: VISUAL_DATA.dataUrl + }, + type: "image_url" + } + ], + role: "user" + } + ]); + assert.equal(Object.hasOwn(messages[0], "tokenCount"), false); + assert.equal(Object.hasOwn(messages[0], "visualData"), false); + assert.deepEqual(sourceMessage.visualData, [VISUAL_DATA]); +}); + +test("non-vision transport preserves text while dropping internal visual metadata", () => { + const messages = prepareChatMessagesForVisionTransport( + [ + { + content: "Text only.", + role: "user", + tokenCount: 12, + visualData: [VISUAL_DATA] + } + ], + { + supportsVision: false + } + ); + + assert.deepEqual(messages, [ + { + content: "Text only.", + role: "user" + } + ]); +}); + +test("consecutive user turns merge text and deduplicate visual ids before token accounting", () => { + const mergedMessages = mergeConsecutiveChatMessages([ + { + content: "First", + role: "user", + visualData: [VISUAL_DATA] + }, + { + content: "Second", + role: "user", + visualData: [VISUAL_DATA, { ...VISUAL_DATA, id: "visual-2", name: "second.png" }] + } + ]); + + assert.equal(mergedMessages.length, 1); + assert.equal(mergedMessages[0].content, "First\n\nSecond"); + assert.deepEqual( + mergedMessages[0].visualData.map((entry) => entry.id), + ["visual-1", "visual-2"] + ); + assert.ok( + countChatMessageTokens(mergedMessages[0], { + model: "gpt-5.4", + supportsVision: true + }) > countChatMessageTokens({ content: mergedMessages[0].content, role: "user" }) + ); +});