Skip to content

V3.1.2 beta.rc2#249

Open
leslielazzarino wants to merge 8 commits into
devfrom
v3.1.2-beta.rc2
Open

V3.1.2 beta.rc2#249
leslielazzarino wants to merge 8 commits into
devfrom
v3.1.2-beta.rc2

Conversation

@leslielazzarino

@leslielazzarino leslielazzarino commented Jun 30, 2026

Copy link
Copy Markdown

Description:

Please provide a brief description of the changes made in this pull request.

Related Issue:

If this pull request is related to any existing issue, please mention the issue number here.

Changes Made:

Please list down the specific changes made in this pull request.

Testing Done:

Please describe the testing that has been done to ensure the changes made in this pull request are functioning as expected.

Screenshots:

If applicable, please provide screenshots or GIFs or videos to visually demonstrate the changes made.

Checklist:

  • I have tested the changes locally.
  • I have self-reviewed the code changes.
  • I have updated the documentation, if necessary.

Summary by CodeRabbit

  • New Features
    • Added a one-time setup flow to install dependencies and optional development hooks.
    • Added an experimental OpenVINO KV cache precision toggle, with updated labels and descriptions.
    • Improved reasoning status display to show live streaming progress and elapsed time.
  • Bug Fixes
    • Reduced oversized remote image data before saving to improve handling of large images.
    • Improved OpenVINO service startup/shutdown reliability and version detection.
  • Chores
    • Added repository pre-commit formatting and linting support.

leslielazzarino and others added 8 commits June 26, 2026 08:49
Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
 - OVMS pinned to weekly build 2026.3.0.8022ddae3 (releaseTag 39717a69b → 8022ddae3).
  - Added a sync helper to read a backend's version/releaseTag from the shipped backend-versions.json.
  - Removed OVMS's hardcoded version/releaseTag defaults, sourcing them from the JSON instead, and added a
  guard against an unset version during download.
  - Fixed package author name/email (Intel® Corporation/TODO → Intel Corporation/webadmin@linux.intel.com).
  - Lowered the Phison preset contextSize from 64000 to 8192.

Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
Signed-off-by: Leslie Lazzarino <leslie.lazzarino@tngtech.com>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds pre-commit hook infrastructure (.pre-commit-config.yaml + install script), hardens the OpenVINO backend service with bundled version resolution, a persisted version marker, robust Windows process termination and extraction, and introduces an experimental INT4 KV cache precision setting end-to-end. It also improves live reasoning display with streaming-aware elapsed time and adds downscaleDataUriTo1MP for remote image handling.

Changes

Pre-commit Hook Setup

Layer / File(s) Summary
Pre-commit config and install script
.pre-commit-config.yaml, WebUI/build/scripts/ensure-precommit-hook.mjs, WebUI/package.json, readme.md
Defines Ruff/ESLint/Prettier hooks; adds ensure-precommit-hook.mjs that idempotently installs the git hook during npm run setup; wires setup and install-hooks scripts into package.json; updates README.

OpenVINO Backend Service Changes

Layer / File(s) Summary
Bundled version resolution and marker persistence
WebUI/electron/remoteUpdates.ts, WebUI/external/backend-versions.json, WebUI/electron/subprocesses/openVINOBackendService.ts
getBundledBackendVersionSync reads backend-versions.json synchronously with a cache; OpenVINOBackendService uses it for default version/releaseTag, writes an installed-version.json marker post-setup, and prefers that marker in getInstalledVersion().
Service lifecycle: stop consolidation and Windows extraction robustness
WebUI/electron/subprocesses/openVINOBackendService.ts
Adds stopAllModelServers() called from stop() and at the start of set_up(); adds terminateProcessTree() used by all per-server stop methods; adds killStrayOvmsProcessesWindows() and removeDirWithRetry() for robust Windows extraction; guards downloadOvms() against unset version.
KV cache precision setting end-to-end
WebUI/src/env.d.ts, WebUI/electron/subprocesses/openVINOBackendService.ts, WebUI/src/assets/js/store/backendServices.ts, WebUI/src/assets/i18n/en-US.json, WebUI/src/components/BackendOptions.vue
Adds ovmsKvCachePrecision to ServiceSettings; updateSettings() applies it and restarts chat servers; --kv_cache_precision is conditionally passed to LLM launch args; Pinia store exposes openvinoKvCacheU4 toggle with a watcher; BackendOptions.vue renders the checkbox with i18n strings.

Live Reasoning Display

Layer / File(s) Summary
Reasoning stream state tracking
WebUI/src/assets/js/store/openAiCompatibleChat.ts
Adds reasoningInProgress and reasoningStartedAt refs; sets them from chunk types using interruption-based block segmentation (replaces time-gap heuristic); clears on turn end and stream finish; exposes both from the store.
Streaming-aware reasoning display
WebUI/src/components/ChatReasoningDisplay.vue, WebUI/src/views/Chat.vue
Adds streaming and liveStartedAt props to ChatReasoningDisplay; uses setInterval to update elapsed seconds while streaming; Chat.vue adds isReasoningStreaming helper and wires the new props.

Data URI Image Downscaling

Layer / File(s) Summary
downscaleDataUriTo1MP and refactored helpers
WebUI/src/lib/utils.ts, WebUI/src/assets/js/store/homeAgent.ts
Extracts shared downscale helpers (blobToDataUri, resolveDownscaleEncoding, downscaleLoadedImageToBlob); refactors downscaleImageTo1MP; adds downscaleDataUriTo1MP; homeAgent applies it to remote image data URIs before persistence.

Sequence Diagram(s)

sequenceDiagram
  participant BackendOptions.vue
  participant backendServices
  participant IPC as updateServiceSettings IPC
  participant OpenVINOBackendService

  BackendOptions.vue->>backendServices: openvinoKvCacheU4 = true
  backendServices->>IPC: ovmsKvCachePrecision = 'u4'
  IPC->>OpenVINOBackendService: updateSettings({ovmsKvCachePrecision: 'u4'})
  OpenVINOBackendService->>OpenVINOBackendService: stopOvmsLlmServer + stopOvmsEmbeddingServer
  Note over OpenVINOBackendService: kvCachePrecision = 'u4'
  OpenVINOBackendService->>OpenVINOBackendService: startOvmsLlmServer(--kv_cache_precision u4)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TNG/AI-Playground#213: Modifies startOvmsLlmServer and related OVMS launch argument logic in openVINOBackendService.ts, directly overlapping with the KV cache precision and server stop/start changes in this PR.

Poem

🐇 Hops through the code with a linting delight,
Pre-commit hooks guarding each line just right.
The OVMS beast now gracefully halts,
KV cache precision corrects all its faults.
Reasoning streams with a ticking clock shown—
The rabbit has tidied what once was overgrown! 🕐

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is only the template text and contains no actual details about the changes, testing, related issue, or screenshots. Fill in the template with a real description, related issue if any, changes made, testing done, and any relevant screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is just a release/version label and does not describe the actual changes in the pull request. Replace it with a concise summary of the main change, such as the primary feature or fix introduced.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.1.2-beta.rc2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
WebUI/src/assets/js/store/homeAgent.ts (1)

1131-1138: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

mediaType/filename extension can diverge from the downscaled bytes.

downscaleDataUriTo1MP re-encodes any source that isn't JPEG/WebP to PNG (via resolveDownscaleEncoding). For an oversized source whose img.mime is e.g. image/gif/image/bmp/image/avif/image/heic, the persisted bytes become PNG while mediaType and the ext still report the original type, so the file part advertises a MIME that no longer matches its content. Common channel formats (jpeg/png/webp) are preserved, so impact is limited to exotic inputs.

Consider deriving the type from the downscaled URI rather than the original img.mime:

♻️ Proposed adjustment
-          const downscaledUri = await downscaleDataUriTo1MP(dataUri)
-          const aipgUrl = await saveImageToMediaInput(downscaledUri)
-          const ext = img.mime === 'image/jpeg' ? 'jpg' : img.mime.replace('image/', '')
+          const downscaledUri = await downscaleDataUriTo1MP(dataUri)
+          const aipgUrl = await saveImageToMediaInput(downscaledUri)
+          const effectiveMime = downscaledUri.match(/^data:([^;,]+)[;,]/)?.[1] ?? img.mime
+          const ext = effectiveMime === 'image/jpeg' ? 'jpg' : effectiveMime.replace('image/', '')
           parts.push({
             type: 'file',
-            mediaType: img.mime,
+            mediaType: effectiveMime,
             url: aipgUrl,
             filename: `${sourceLabel}-${Date.now()}-${i}.${ext}`,
           })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/src/assets/js/store/homeAgent.ts` around lines 1131 - 1138, The file
part in homeAgent.ts is using the original img.mime to set mediaType and
filename extension, but downscaleDataUriTo1MP can re-encode some inputs to PNG,
so the advertised type may not match the saved bytes. Update the logic around
saveImageToMediaInput and the parts.push payload to derive the output
MIME/extension from the downscaled data URI (or from the downscale helper’s
chosen encoding) instead of img.mime, while keeping the existing behavior for
JPEG/WebP and matching the filename suffix to the actual persisted format.
WebUI/electron/subprocesses/openVINOBackendService.ts (1)

1724-1749: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Surface stop failures from stop().

stopAllModelServers() swallows every exception, then stop() always returns 'stopped'. apiServiceRegistry.stopAllServices() will treat that as success, so leaked OVMS workers can keep ports or files locked while the app reports a clean shutdown. Please propagate an aggregate failure here and keep the best-effort behavior only for the reinstall path if that's intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/electron/subprocesses/openVINOBackendService.ts` around lines 1724 -
1749, `stopAllModelServers()` is currently swallowing all stop errors, so
`stop()` always reports a clean shutdown even when one or more OVMS servers fail
to stop; update `stopAllModelServers()` and the `stop()` flow in
`openVINOBackendService` to collect any failures and surface an aggregate error
or failure status back to the caller instead of always returning success. Keep
the existing best-effort per-server shutdown behavior, but ensure `stop()` can
fail when any of the `stopOvmsLlmServer`, `stopOvmsEmbeddingServer`,
`stopOvmsTranscriptionServer`, `stopOvmsSpeechServer`, or `stopOvmsImageServer`
calls fail so `apiServiceRegistry.stopAllServices()` can detect the failure.
🧹 Nitpick comments (1)
WebUI/src/env.d.ts (1)

72-73: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Narrow ovmsKvCachePrecision to the supported literals.

This IPC contract currently accepts any string, but WebUI/electron/subprocesses/openVINOBackendService.ts forwards non-empty values straight into --kv_cache_precision. Typing it as '' | 'u8' | 'u4' | 'f16' | 'fp32' keeps renderer and main in sync and prevents invalid launch args from compiling.

Proposed fix
+type OvmsKvCachePrecision = '' | 'u8' | 'u4' | 'f16' | 'fp32'
+
 type ServiceSettings = {
   serviceName: BackendServiceName
   version?: string
   releaseTag?: string
   comfyUiParameters?: string
   llamaCppParameters?: string
   llamaCppBuildVariant?: 'standard' | 'ssd-offload'
   llamaCppOffloadDrive?: string | null
   // OVMS --kv_cache_precision value ('u8' | 'u4' | 'f16' | 'fp32'); '' = OVMS default.
-  ovmsKvCachePrecision?: string
+  ovmsKvCachePrecision?: OvmsKvCachePrecision
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/src/env.d.ts` around lines 72 - 73, The IPC contract for
ovmsKvCachePrecision is too broad and should be narrowed to the supported
literals. Update the type in env.d.ts so it only allows '' | 'u8' | 'u4' | 'f16'
| 'fp32', matching what openVINOBackendService forwards into
--kv_cache_precision, and keep the renderer/main contract in sync so invalid
launch arguments are rejected at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.pre-commit-config.yaml:
- Around line 26-44: The formatter hooks are still treated as failures when they
modify files, so the current ruff-format and prettier entries in the pre-commit
config will reject the first commit after reformatting. Update the hook setup
for ruff-format and prettier so they use a pre-commit-compatible
format-and-restage approach (for example via a proper wrapper or
pre-commit-managed stages) rather than relying on plain git add after
formatting, and keep the existing hook names/files patterns so they remain easy
to locate.

In `@WebUI/build/scripts/ensure-precommit-hook.mjs`:
- Around line 71-84: The fallback in ensure-precommit-hook.mjs only checks
ambient PATH for pre-commit and uvx, so it misses the uv binary downloaded by
setup. Update the runner selection logic in ensure-precommit-hook.mjs to detect
and use the repo-provided uv toolchain before falling back to system commands,
then invoke pre-commit through that binary when available. Keep the existing
commandAvailable flow and preserve the current user-facing failure message only
for the true no-tool case.

In `@WebUI/electron/subprocesses/openVINOBackendService.ts`:
- Around line 2168-2177: Keep the OVMS process object tracked until exit is
actually confirmed. In `terminateProcessTree`, when the second `waitForExit()`
times out, do not just log and resolve; return a failure signal (or otherwise
indicate the process is still alive) so `stopOvmsLlmServer` can avoid clearing
`this.ovmsLlmProcess` prematurely. Update `stopOvmsLlmServer` and the
`terminateProcessTree` flow to preserve `this.ovmsLlmProcess` until the `exit`
event is observed.
- Around line 1594-1602: Scope the Windows cleanup in
killStrayOvmsProcessesWindows so it only targets OVMS processes started by this
service instead of using a system-wide taskkill on ovms.exe. Update the logic to
identify and terminate only tracked child PIDs or processes whose executable
path is under this.ovmsDir, and keep the existing logging in
openVINOBackendService aligned with the narrowed scope. Ensure the change
preserves the current non-fatal handling when nothing matching is found.
- Around line 1247-1255: The version marker is currently written from the
requested values instead of the actual downloaded artifact metadata, which can
make getInstalledVersion() report the wrong release tag. Update
openVINOBackendService’s download flow so the selected artifact metadata is
captured when the download URL is chosen in downloadOvms(), then have
writeVersionMarker() persist that stored artifact version/releaseTag instead of
this.version/this.releaseTag. Keep the fix localized to the download selection
and writeVersionMarker() path so the marker always reflects what was actually
installed.

In `@WebUI/src/assets/js/store/backendServices.ts`:
- Around line 419-435: The OpenVINO KV-cache sync failure is only logged with
console.warn inside the watch handler for openvinoKvCacheU4, so it bypasses the
app’s error pipeline. Update that catch path to route the caught error through
the errors store via errors.report(err, overrides?) instead of warning directly,
using a non-blocking surface such as silent or warn. Keep the sync logic in
backendServices.ts and the updateServiceSettings call intact, but ensure
failures are reported consistently through useErrors.

In `@WebUI/src/assets/js/store/openAiCompatibleChat.ts`:
- Around line 96-109: The reasoning stream state in openAiCompatibleChat.ts is
currently shared globally via reasoningInProgress and reasoningStartedAt, so a
generate() call for another conversationKey can overwrite the active chat’s
timer state. Scope these refs by conversationKey inside the store and update the
generate()/chunk-stream handling to read and write per-conversation state, then
expose the active conversation’s derived reasoning values to Chat.vue instead of
a single shared pair.

---

Outside diff comments:
In `@WebUI/electron/subprocesses/openVINOBackendService.ts`:
- Around line 1724-1749: `stopAllModelServers()` is currently swallowing all
stop errors, so `stop()` always reports a clean shutdown even when one or more
OVMS servers fail to stop; update `stopAllModelServers()` and the `stop()` flow
in `openVINOBackendService` to collect any failures and surface an aggregate
error or failure status back to the caller instead of always returning success.
Keep the existing best-effort per-server shutdown behavior, but ensure `stop()`
can fail when any of the `stopOvmsLlmServer`, `stopOvmsEmbeddingServer`,
`stopOvmsTranscriptionServer`, `stopOvmsSpeechServer`, or `stopOvmsImageServer`
calls fail so `apiServiceRegistry.stopAllServices()` can detect the failure.

In `@WebUI/src/assets/js/store/homeAgent.ts`:
- Around line 1131-1138: The file part in homeAgent.ts is using the original
img.mime to set mediaType and filename extension, but downscaleDataUriTo1MP can
re-encode some inputs to PNG, so the advertised type may not match the saved
bytes. Update the logic around saveImageToMediaInput and the parts.push payload
to derive the output MIME/extension from the downscaled data URI (or from the
downscale helper’s chosen encoding) instead of img.mime, while keeping the
existing behavior for JPEG/WebP and matching the filename suffix to the actual
persisted format.

---

Nitpick comments:
In `@WebUI/src/env.d.ts`:
- Around line 72-73: The IPC contract for ovmsKvCachePrecision is too broad and
should be narrowed to the supported literals. Update the type in env.d.ts so it
only allows '' | 'u8' | 'u4' | 'f16' | 'fp32', matching what
openVINOBackendService forwards into --kv_cache_precision, and keep the
renderer/main contract in sync so invalid launch arguments are rejected at
compile time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01a2a78c-54d3-4624-aa55-0be5df9722f3

📥 Commits

Reviewing files that changed from the base of the PR and between cc59d5d and 7f3ef0d.

📒 Files selected for processing (17)
  • .pre-commit-config.yaml
  • WebUI/build/scripts/ensure-precommit-hook.mjs
  • WebUI/electron/remoteUpdates.ts
  • WebUI/electron/subprocesses/openVINOBackendService.ts
  • WebUI/external/backend-versions.json
  • WebUI/package.json
  • WebUI/src/assets/i18n/en-US.json
  • WebUI/src/assets/js/store/backendServices.ts
  • WebUI/src/assets/js/store/homeAgent.ts
  • WebUI/src/assets/js/store/openAiCompatibleChat.ts
  • WebUI/src/components/BackendOptions.vue
  • WebUI/src/components/ChatReasoningDisplay.vue
  • WebUI/src/env.d.ts
  • WebUI/src/lib/utils.ts
  • WebUI/src/views/Chat.vue
  • modes/base/presets/phison.json
  • readme.md

Comment thread .pre-commit-config.yaml
Comment on lines +26 to +44
# Formatter (Ruff) — format in place, restage, never fail on reformat
- id: ruff-format
name: ruff format (Python)
language: system
entry: bash -c 'uvx ruff@0.15.20 format "$@" && git add -- "$@"' --
files: ^(service|home-agent)/.*\.py$
# Linter (ESLint with auto-fix)
- id: eslint
name: eslint (WebUI)
language: system
entry: bash -c 'cd WebUI && npm run lint:eslint'
files: ^WebUI/.*\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue)$
pass_filenames: false
# Formatter (Prettier) — format in place, restage, never fail on reformat
- id: prettier
name: prettier (WebUI)
language: system
entry: bash -c 'cd WebUI && files=(); for f in "$@"; do files+=("${f#WebUI/}"); done && npx prettier --write "${files[@]}" && cd .. && git add -- "$@"' --
files: ^WebUI/.*\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|vue|json|css|scss|html|md|ya?ml)$

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does pre-commit mark a hook as failed when it modifies files, even if the hook exits successfully and restages the files with git add?

💡 Result:

Yes, pre-commit marks a hook as failed if it modifies files, even if the hook exits with a successful (zero) exit code [1][2][3]. The pre-commit framework explicitly considers a hook to have failed if either it returns a non-zero exit code or if it modifies files during its execution [1][2]. The framework detects these modifications by comparing the state of the repository before and after the hook runs using git diff [4][2]. This behavior is a deliberate design choice rather than a bug [5][4][2]. The rationale is that if a hook modifies files, the commit is no longer in the state the user intended to commit, and the framework intentionally forces the user to review the changes, manually stage them (if desired), and re-commit [5][6]. Attempting to automatically stage files modified by a hook is intentionally not supported, as it could lead to unexpected behavior, such as accidentally staging unrelated unstaged changes, overwriting intentional manual changes, or committing code that hasn't been properly vetted by the human user [5][6]. If you use a hook that modifies files (like a code formatter), the standard workflow is to let the hook fail, then stage the modified files and run the commit again [5][6][7]. Because the changes are now in the staging area, the next attempt will typically pass, provided the formatter is consistent and doesn't introduce further changes [5][2].

Citations:


git add won’t stop pre-commit from failing on formatter changes. pre-commit still marks a hook as failed when it modifies tracked files, even if it exits 0 and restages them, so the first formatting pass will reject the commit. If the goal is a single-pass format-and-commit flow, this needs a different integration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.pre-commit-config.yaml around lines 26 - 44, The formatter hooks are still
treated as failures when they modify files, so the current ruff-format and
prettier entries in the pre-commit config will reject the first commit after
reformatting. Update the hook setup for ruff-format and prettier so they use a
pre-commit-compatible format-and-restage approach (for example via a proper
wrapper or pre-commit-managed stages) rather than relying on plain git add after
formatting, and keep the existing hook names/files patterns so they remain easy
to locate.

Comment on lines +71 to +84
// Prefer a `pre-commit` on PATH; otherwise run it through uv's `uvx`.
let runner
if (commandAvailable('pre-commit')) {
runner = ['pre-commit']
} else if (commandAvailable('uvx')) {
runner = ['uvx', 'pre-commit']
} else {
log('`pre-commit` is not installed and `uvx` is unavailable, so the hook was not installed.')
log('Install it once with either of:')
log(' uv tool install pre-commit')
log(' pipx install pre-commit')
log('then re-run `npm run setup` (or `pre-commit install`).')
process.exit(0)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

This never uses the repo-downloaded uv toolchain.

npm run setup fetches uv immediately before invoking this script, but this fallback only checks pre-commit and uvx on the ambient PATH. Fresh clones without a global install will still hit the “unavailable” branch instead of using the downloaded binary, so the auto-install path is weaker than the README now promises.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/build/scripts/ensure-precommit-hook.mjs` around lines 71 - 84, The
fallback in ensure-precommit-hook.mjs only checks ambient PATH for pre-commit
and uvx, so it misses the uv binary downloaded by setup. Update the runner
selection logic in ensure-precommit-hook.mjs to detect and use the repo-provided
uv toolchain before falling back to system commands, then invoke pre-commit
through that binary when available. Keep the existing commandAvailable flow and
preserve the current user-facing failure message only for the true no-tool case.

Comment on lines +1247 to +1255
private async writeVersionMarker(): Promise<void> {
try {
await filesystem.writeJson(this.versionMarkerPath, {
version: this.version,
...(this.releaseTag && { releaseTag: this.releaseTag }),
})
} catch (e) {
this.appLogger.warn(`Failed to write OpenVINO version marker: ${e}`, this.name)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist the downloaded artifact metadata, not the requested values.

writeVersionMarker() currently serializes this.version / this.releaseTag, but those are just the requested settings. downloadOvms() can fall back to a different asset, and getInstalledVersion() now trusts this marker first, so the app can report a release tag that was never actually installed. Capture the selected artifact metadata when the download URL is chosen and write that instead.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { ChildProcess, spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/electron/subprocesses/openVINOBackendService.ts` around lines 1247 -
1255, The version marker is currently written from the requested values instead
of the actual downloaded artifact metadata, which can make getInstalledVersion()
report the wrong release tag. Update openVINOBackendService’s download flow so
the selected artifact metadata is captured when the download URL is chosen in
downloadOvms(), then have writeVersionMarker() persist that stored artifact
version/releaseTag instead of this.version/this.releaseTag. Keep the fix
localized to the download selection and writeVersionMarker() path so the marker
always reflects what was actually installed.

Comment on lines +1594 to +1602
private async killStrayOvmsProcessesWindows(): Promise<void> {
if (process.platform !== 'win32') return
try {
await execAsync('taskkill /F /IM ovms.exe /T')
this.appLogger.info('Killed stray ovms.exe processes before extraction', this.name)
} catch (e) {
// Exits non-zero when no ovms.exe is running — expected and not fatal.
this.appLogger.info(`No stray ovms.exe to kill (or taskkill reported: ${e})`, this.name)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don't kill every ovms.exe on the machine.

taskkill /IM ovms.exe /T /F is system-wide. During setup it will also terminate unrelated OVMS instances the user started outside AI Playground, which makes reinstall destructive for other local workloads. Scope this to processes owned by this service (tracked child PIDs, or processes whose executable path lives under this.ovmsDir).

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { ChildProcess, spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/electron/subprocesses/openVINOBackendService.ts` around lines 1594 -
1602, Scope the Windows cleanup in killStrayOvmsProcessesWindows so it only
targets OVMS processes started by this service instead of using a system-wide
taskkill on ovms.exe. Update the logic to identify and terminate only tracked
child PIDs or processes whose executable path is under this.ovmsDir, and keep
the existing logging in openVINOBackendService aligned with the narrowed scope.
Ensure the change preserves the current non-fatal handling when nothing matching
is found.

Comment on lines +2168 to +2177
exited = await waitForExit(5000)
if (!exited) {
this.appLogger.warn(`OVMS ${label} not confirmed exited after force kill`, this.name)
}
}

private async stopOvmsLlmServer(): Promise<void> {
if (this.ovmsLlmProcess) {
this.appLogger.info(`Stopping OVMS LLM server for model: ${this.currentModel}`, this.name)
this.ovmsLlmProcess.process.kill('SIGTERM')

// Wait a bit for graceful shutdown, then force kill if needed
await new Promise<void>((resolve) => {
const currentProcess = this.ovmsLlmProcess
const timeout = setTimeout(() => {
if (currentProcess) {
this.appLogger.warn(`Force killing OVMS LLM server process`, this.name)
currentProcess.process.kill('SIGKILL')
}
resolve()
}, 5000)

if (currentProcess) {
currentProcess.process.on('exit', () => {
clearTimeout(timeout)
resolve()
})
} else {
clearTimeout(timeout)
resolve()
}
})
await this.terminateProcessTree(this.ovmsLlmProcess.process, 'LLM server')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the process tracked until exit is actually confirmed.

When the second waitForExit() times out, terminateProcessTree() only logs and returns. stopOvmsLlmServer() then clears this.ovmsLlmProcess anyway, so an unresponsive server can stay alive after we've dropped the last reference to it. Return a failure here instead of resolving, or keep the process object until the exit event fires.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { ChildProcess, spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/electron/subprocesses/openVINOBackendService.ts` around lines 2168 -
2177, Keep the OVMS process object tracked until exit is actually confirmed. In
`terminateProcessTree`, when the second `waitForExit()` times out, do not just
log and resolve; return a failure signal (or otherwise indicate the process is
still alive) so `stopOvmsLlmServer` can avoid clearing `this.ovmsLlmProcess`
prematurely. Update `stopOvmsLlmServer` and the `terminateProcessTree` flow to
preserve `this.ovmsLlmProcess` until the `exit` event is observed.

Comment on lines +419 to +435
/** Sync the OpenVINO KV cache precision toggle to the main process so a
* running OVMS service reloads its chat server with the new precision. */
watch(
openvinoKvCacheU4,
async () => {
try {
await updateServiceSettings({
serviceName: 'openvino-backend',
ovmsKvCachePrecision: effectiveOvmsKvCachePrecision.value,
})
} catch (e) {
console.warn('Failed to sync OpenVINO KV cache precision to main process:', e)
}
},
{ flush: 'post' },
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Report KV-cache sync failures through the errors store.

If this IPC update fails, the renderer toggle changes while the main process keeps the old precision. Logging a warning leaves that mismatch outside the app’s error pipeline; route it through errors.report(...) with a silent or warn surface instead.

As per coding guidelines, "All errors must flow through the errors store (useErrors). Report errors via errors.report(err, overrides?)" and "Use surface control in errors: 'toast' (default, user-facing), 'inline', 'modal', or 'silent' (background work, no UI)."

Proposed fix
+import { createAppError } from '`@/assets/js/errors/appError`'
+import { useErrors } from '`@/assets/js/store/errors`'
+
 export const useBackendServices = defineStore(
   'backendServices',
   () => {
+    const errors = useErrors()
+
     // ...
     watch(
       openvinoKvCacheU4,
       async () => {
         try {
           await updateServiceSettings({
             serviceName: 'openvino-backend',
             ovmsKvCachePrecision: effectiveOvmsKvCachePrecision.value,
           })
         } catch (e) {
-          console.warn('Failed to sync OpenVINO KV cache precision to main process:', e)
+          errors.report(
+            createAppError({
+              category: 'backend',
+              code: 'openvino/kv-cache-sync-failed',
+              userMessage: 'Failed to sync the OpenVINO KV cache setting.',
+              surface: 'silent',
+              severity: 'warn',
+              cause: e,
+            }),
+          )
         }
       },
       { flush: 'post' },
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Sync the OpenVINO KV cache precision toggle to the main process so a
* running OVMS service reloads its chat server with the new precision. */
watch(
openvinoKvCacheU4,
async () => {
try {
await updateServiceSettings({
serviceName: 'openvino-backend',
ovmsKvCachePrecision: effectiveOvmsKvCachePrecision.value,
})
} catch (e) {
console.warn('Failed to sync OpenVINO KV cache precision to main process:', e)
}
},
{ flush: 'post' },
)
import { createAppError } from '`@/assets/js/errors/appError`'
import { useErrors } from '`@/assets/js/store/errors`'
export const useBackendServices = defineStore(
'backendServices',
() => {
const errors = useErrors()
/** Sync the OpenVINO KV cache precision toggle to the main process so a
* running OVMS service reloads its chat server with the new precision. */
watch(
openvinoKvCacheU4,
async () => {
try {
await updateServiceSettings({
serviceName: 'openvino-backend',
ovmsKvCachePrecision: effectiveOvmsKvCachePrecision.value,
})
} catch (e) {
errors.report(
createAppError({
category: 'backend',
code: 'openvino/kv-cache-sync-failed',
userMessage: 'Failed to sync the OpenVINO KV cache setting.',
surface: 'silent',
severity: 'warn',
cause: e,
}),
)
}
},
{ flush: 'post' },
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/src/assets/js/store/backendServices.ts` around lines 419 - 435, The
OpenVINO KV-cache sync failure is only logged with console.warn inside the watch
handler for openvinoKvCacheU4, so it bypasses the app’s error pipeline. Update
that catch path to route the caught error through the errors store via
errors.report(err, overrides?) instead of warning directly, using a non-blocking
surface such as silent or warn. Keep the sync logic in backendServices.ts and
the updateServiceSettings call intact, but ensure failures are reported
consistently through useErrors.

Source: Coding guidelines

Comment on lines +96 to +109
// True while the model is actively emitting reasoning (i.e. the last content
// chunk was a reasoning delta and no text/tool chunk has followed). Driven
// straight off the chunk stream so the UI never has to infer "is reasoning
// still going?" from part positions or the per-delta `reasoningFinished`
// timestamp (which is bumped to "now" on every delta and so always looks
// recent). Cleared when the turn ends (see the `processing` safety-net watch).
const reasoningInProgress = ref(false)
// Wall-clock start of the reasoning block currently in progress. The part's
// own `reasoningStarted` metadata isn't attached to the live (still-
// streaming) UI part, so the chat view needs this to drive an increasing
// "Reasoned for X.Xs" timer; once the block finishes the view falls back to
// the part metadata. Updated to the block start whenever a new block begins.
const reasoningStartedAt = ref(0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Scope reasoning stream state by conversation.

useHomeAgent can call generate() for a different conversationKey while the desktop chat is open, but these new refs are store-global. A background stream can therefore flip reasoningInProgress/reasoningStartedAt for the active chat, which makes Chat.vue show a live timer on the wrong reasoning block or with the wrong start time. Please key this state by conversationKey and expose the active conversation's derived values instead of sharing one global pair across all chats.

Also applies to: 684-696, 745-761, 806-806, 1173-1174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@WebUI/src/assets/js/store/openAiCompatibleChat.ts` around lines 96 - 109, The
reasoning stream state in openAiCompatibleChat.ts is currently shared globally
via reasoningInProgress and reasoningStartedAt, so a generate() call for another
conversationKey can overwrite the active chat’s timer state. Scope these refs by
conversationKey inside the store and update the generate()/chunk-stream handling
to read and write per-conversation state, then expose the active conversation’s
derived reasoning values to Chat.vue instead of a single shared pair.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants