build(deps-dev): bump @adonisjs/tsconfig from 1.4.1 to 2.0.0 in /admin#843
Open
dependabot[bot] wants to merge 108 commits into
Open
build(deps-dev): bump @adonisjs/tsconfig from 1.4.1 to 2.0.0 in /admin#843dependabot[bot] wants to merge 108 commits into
dependabot[bot] wants to merge 108 commits into
Conversation
When the OpenAI-compatible fallback (/v1/models) is used, models are mapped as { name: m.id, size: 0 } with no details field. Accessing model.details.parameter_size throws `TypeError: Cannot read properties of undefined`, which crashes the React render and causes the entire page to go blank.
Use the currently loaded model for chat title generation and query rewrite.
…ogs (#741) Corrupted ZIM files cause a native C++ abort (ZimFileFormatError) that bypasses JS try/catch and kills the process. Add magic number validation before passing files to @openzim/libzim so invalid files are skipped gracefully. Also deduplicate Ollama download progress broadcasts — both within a single stream (skip unchanged percentages) and across concurrent callers (share one download promise per model). Co-authored-by: aegisman <aegis@manicode.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Ben Smith <bravosierra99@gmail.com>
Co-Authored-By: Ben Smith <bravosierra99@gmail.com>
…ial files
Downloads are now written to `filepath + '.tmp'` and atomically renamed
to the final path only on successful completion. Kiwix globs for `*.zim`
and ZimService filters `.endsWith('.zim')`, so `.tmp` files are invisible
to both during download. The same staging applies to `.pmtiles` map files.
Ref #372
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on (CWE-918, CWE-209) (#552) * fix(security): add SSRF validation to map download URLs from manifest * fix(security): sanitize verbose error in rag controller scan endpoint * fix(security): sanitize verbose errors in benchmark controller * fix(security): sanitize verbose error in system controller version fetch * fix(security): sanitize verbose errors in chats controller (6 instances) * fix(security): sanitize verbose errors in docker service (6 instances) * fix(security): sanitize verbose error in system update service * fix(security): sanitize verbose errors in collection update service --------- Co-authored-by: Jake Turner <52841588+jakeaturner@users.noreply.github.com>
…729) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.11 to 1.16.0. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](follow-redirects/follow-redirects@v1.15.11...v1.16.0) --- updated-dependencies: - dependency-name: follow-redirects dependency-version: 1.16.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [axios](https://github.com/axios/axios) from 1.13.5 to 1.15.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](axios/axios@v1.13.5...v1.15.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.15.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…in (#736) Bumps [protocol-buffers-schema](https://github.com/mafintosh/protocol-buffers-schema) from 3.6.0 to 3.6.1. - [Commits](mafintosh/protocol-buffers-schema@v3.6.0...v3.6.1) --- updated-dependencies: - dependency-name: protocol-buffers-schema dependency-version: 3.6.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.5.4 to 7.5.5. - [Release notes](https://github.com/protobufjs/protobuf.js/releases) - [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md) - [Commits](protobufjs/protobuf.js@protobufjs-v7.5.4...protobufjs-v7.5.5) --- updated-dependencies: - dependency-name: protobufjs dependency-version: 7.5.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#724) Bumps [@adonisjs/http-server](https://github.com/adonisjs/http-server) from 7.8.0 to 7.8.1. - [Release notes](https://github.com/adonisjs/http-server/releases) - [Commits](adonisjs/http-server@v7.8.0...v7.8.1) --- updated-dependencies: - dependency-name: "@adonisjs/http-server" dependency-version: 7.8.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.4.1 to 6.4.2. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 6.4.2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.23...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#753) Introduces a dedicated page listing third-party ZIM content packs built by the community. Launches with the two current add-ons (jrsphoto field manuals, kennethbrewer W3Schools) and explains how to install a ZIM pack and where to submit a new one for inclusion. - New doc at admin/docs/community-add-ons.md - Wired into DocsService DOC_ORDER (slot 4) and TITLE_OVERRIDES so the hyphen in "Add-Ons" is preserved in the sidebar - README gets a link under Community & Resources Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qdrant's upstream default enables anonymous telemetry to telemetry.qdrant.io, which doesn't match NOMAD's offline-first "zero telemetry" posture. Adding QDRANT__TELEMETRY_DISABLED=true to the container environment turns it off for fresh installs and reinstalls. Existing installs keep their current telemetry-enabled container until the Qdrant service is force-reinstalled via the Knowledge Base panel or the next container recreation — Docker bakes Env into containers at create time, so env changes require a new container. Closes #742 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…igured (#744) When users set a remote Ollama URL via AI Settings, the local nomad_ollama container continued running and competed with the remote host for port 11434 and GPU access. Now configureRemote stops the local container on set and restores it on clear (if still present). Container and its models volume are preserved so the local install can be re-enabled later. Closes #662 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…orer (#746) When many ZIMs are already installed locally, a single Kiwix catalog page (12 items) could return 12 already-installed items, which zim_service would fully filter out client-side. The endpoint returned items: [] with has_more: true, and the frontend's infinite-scroll guard (flatData.length > 0) blocked fetchNextPage — leaving the user with "No records found" despite plenty of uninstalled ZIMs available. Backend now accumulates across up to 5 Kiwix fetches (60 items each) until it has enough post-filter results to return, dedupes by entry id, advances currentStart by actual entries returned (not requested), and returns a next_start cursor. The frontend consumes that cursor instead of computing Kiwix offsets locally, and the flatData.length > 0 guard is removed so the existing on-mount effect drives bounded auto-fetch when a short page lands. The pre-existing has_more off-by-one (compared totalResults against the input start rather than the post-fetch position) is fixed implicitly. Diagnosis credit: @johno10661. Closes #731 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… walk) (#745) Three bugs in the RAG embedding pipeline, diagnosed and patched by @sbruschke against v1.31.0 with working before/after chunk counts. All three are root-cause contributors to #388. 1. scanAndSyncStorage queued every file under /storage/zim/ for embedding, including Kiwix's generated kiwix-library.xml. EmbedFileJob rejected it with "Unsupported file type" and the default 30-attempt retry policy kept it looping on every sync, flooding nomad_admin logs. Now gated on determineFileType(filePath) !== 'unknown'. 2. hasMoreBatches compared zimChunks.length (section-level chunk count under the 'structured' strategy) against ZIM_BATCH_SIZE (an article limit). Because articles emit multiple sections, the two are never equal for real archives and processing silently stopped after the first 50 articles. Now gated on articlesInBatch >= ZIM_BATCH_SIZE. 3. extractStructuredContent walked only direct children of <body>, so any ZIM that wraps content in a container div (Devdocs, Wikipedia, FreeCodeCamp, React docs, etc.) produced zero sections and silently embedded zero chunks while reporting success. Now walks the full DOM via $('body').find('h2, h3, h4, p, ul, ol, dl, table'), with a whole-body text fallback when the selector walk yields nothing. Before/after chunk counts confirmed by @sbruschke on v1.31.0: devdocs_en_git 0 -> 916 devdocs_en_react 0 -> 481 devdocs_en_node 0 -> 423 libretexts_en_eng 1 -> 35 (climbing) Wikipedia resumed progressing normally through its 6M articles. Closes #718 Closes #719 Closes #720 Closes #388 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Dockerfile copied root package.json to /app/version.json, which SystemService.getAppVersion() reads on every render of the app version in the UI. semantic-release only reliably commits that bump back on the main branch; on the rc branch it does not, so v1.31.1-rc.1 and v1.31.1-rc.2 both shipped with a version.json still reading 1.31.0. Result: a user who upgrades to rc.2 sees "1.31.0" in the UI and a persistent "update to v1.31.1-rc.2 available" prompt. The build workflow already passes VERSION as a build-arg (used today only for the OCI image label). Generating version.json from that arg at build time makes the image tag the single source of truth and eliminates the drift, regardless of what the committed-back package.json says. Dev builds (no VERSION override) write "dev", which matches the existing NODE_ENV=development short-circuit in getAppVersion(). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tightens the existing "open an issue first" guidance: non-trivial PRs must reference a corresponding issue and may be closed without one. Adds an explicit carveout for trivial fixes (typos, doc clarifications, small one-line bugs) so drive-by improvements still flow through.
…images (#797) Detects the host architecture early in the preflight sequence. On any architecture other than x86_64/amd64, prints a 5-line warning that NOMAD officially supports x86_64 only, points at PR #419, and sleeps 10 seconds before continuing. Ctrl+C aborts cleanly before any Docker work happens. Preserves the community/hacker path: ARM64 users running with QEMU binfmt_misc emulation can still let the install proceed. The change just stops the silent 2.7GB amd64 pull on architectures where it will not work, which leaves partial images and /opt/project-nomad/ debris that confuse first-time users. Reported in #782.
Closes #685 Content Manager now surfaces the on-disk size of each ZIM file alongside title/summary, and lets users sort the list by Size or Title. Defaults to Size descending so the largest files are visible first. - ZimService.list() now stats each file and returns size_bytes - Content Manager table adds a formatted Size column (via formatBytes) - Sortable headers for Title and Size with asc/desc toggle
* fix(stream): skip compression for Server-Sent Events The global compression middleware (added in v1.31.0-rc.2) buffers response writes to determine encoding, which collapses per-token streaming into a single block delivered after generation completes. This broke the AI chat streaming UX from v1.31.0-rc.2 onward — text no longer appears progressively as the model generates it, only at the end. Adds a filter to compression() that returns false when the response Content-Type is text/event-stream. Other responses still go through the default compression filter (compressible types are still compressed; e.g. text/html via Brotli). Reproduced on NOMAD3 v1.31.1: before fix, all SSE chunks for a 1B model arrive within 10ms of each other after the model finishes. After fix, tokens arrive at ~150ms intervals as they're generated on a 12B model, with no Content-Encoding header on the SSE response. Verified on the same host that /home still returns Content-Encoding: br for HTML responses. Closes #781. Reported and bisected by @toasterking (works in v1.31.0-rc.1, broken from v1.31.0-rc.2 onward). * fix(stream): use any for filter params to match existing as-any pattern The compression library types its filter as (req: Request, res: Response) expecting Express types, but AdonisJS passes raw IncomingMessage/ServerResponse which is why the surrounding middleware uses `as any` casts at the call site. The IncomingMessage/ServerResponse types I added are runtime-correct but fail tsc against the library's declared types. Drop the typed import in favor of `any` parameters, which matches how the existing `compress(request.request as any, response.response as any, ...)` call resolves the same mismatch.
Some Ollama installs ship nomic-embed-text:v1.5 with the embedding model's default num_ctx=2048, which the RAG chunker (sized for ~1500 tokens of estimated content with ratio=2 chars/token) can exceed on dense PDFs. The result is `400 the input length exceeds the context length` from /api/embed, which then hits the OpenAI-compatible fallback (which also errors), and surfaces as a BadRequestError. Pass options.num_ctx=8192 (nomic-embed-text v1.5's RoPE-extrapolated max) and truncate=true (silent truncation safety net) on every embed call so we don't depend on the local modelfile defaults. Reported on #756 by @NC4WD; same root cause as #369 and #670 which were closed without an actual fix. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#770) The VineJS validators in createMarker and updateMarker silently dropped fields not in their schema. The MapMarker model and DB include notes and marker_type, and GET responses return them, but POST and PATCH would not persist them. updateMarker additionally did not accept latitude/longitude, so markers could not be repositioned via the API after creation. - Add notes and marker_type to both validators and model assignments. - Add latitude/longitude to the update validator. - Add coordinate range validation on both endpoints. Closes #768
…healthy (PR #895 review) `computeFileWarnings()` previously caught all errors and returned an empty map, which the frontend rendered as "every file is healthy" — reintroducing exactly the silent-failure mode this surface exists to expose. Return `{ ok, warnings }`; flip `ok: false` from the catch. KB modal renders an inline amber notice under the Stored Files header when `ok === false`, leaving per-row warning rendering untouched. Transient failures self-heal on the next 30s poll; no toast spam.
useEmbedJobs already polls every 2s while jobs are active (and 30s when idle) and auto-invalidates Stored Files when the queue drains. The manual Refresh button was a no-op signal — it just confuses users who click it and see no change. Per-job 'last activity Xs ago' lines remain as the live-recency indicator. Stacks on feat/kb-job-status-pill (#893) since the Refresh button only exists in that branch.
Closes the 'zero_chunks warning has no row to attach to' gap surfaced by the 2026-05-14 integration UAT. Before this fix RagService.getStoredFiles returned only file paths that appeared in Qdrant's payload.source — so files with 0 embedded chunks (video-only ZIMs, browse_only opt-outs, ingestions that failed before producing any chunks) silently disappeared from the KB panel's Stored Files table. The fix unions the Qdrant scroll result with the disk-backed file paths recorded in kb_ingest_state. Effect: - lrnselfreliance_en_all_2025-12.zim (3.97 GB video-only ZIM, 0 chunks) now appears in the table, picks up its zero_chunks warning chip - Files in pending_decision under Manual policy show up so the user can see what's waiting for opt-in - Files in browse_only / failed states have a row for future per-card Retry / Re-index actions (forthcoming, blocked on #886) The state-machine query is wrapped in its own try/catch so a transient DB error degrades to the Qdrant-only list rather than 500-ing the whole panel — same defensive posture as the outer try/catch. Stacks on feat/kb-ingest-state-machine (#888) because the union depends on the kb_ingest_state table that PR introduces. Will rebase to rc once #888 merges. Completes the second half of #895's warning surface; the first half (partial_stall) already worked because those files have at least some chunks in Qdrant.
…ask 12)
When a user opens AI Chat with content available but no global ingest
policy yet recorded, surface a one-time banner above the chat header
asking how they want new content handled:
- 'Index existing content' -> sets rag.defaultIngestPolicy=Always and
triggers a sync so pending_decision files queue immediately
- 'Maybe later' -> sets policy=Manual; existing and future content
waits in pending_decision until the user opts in from the KB modal
After either button is clicked the banner never reappears, because both
write the policy KV (the same one #894 manages via the KB modal toggle).
There is intentionally no 'dismiss without deciding' X — that would just
re-show the banner forever.
Backend
- New GET /api/rag/policy-prompt-state returns
{shouldPrompt, hasContent, totalFiles}
- RagService.getPolicyPromptState() reads KVStore('rag.defaultIngestPolicy')
and counts kb_ingest_state rows; shouldPrompt is true only when policy
is null AND scanner has seen >=1 file (avoids prompting on empty NOMADs)
Frontend
- New KbPolicyPromptBanner component (~120 LOC) handles the two-button
decision flow with optimistic loading state, success/error toasts, and
invalidates kbPolicyPromptState + ingestPolicy + embed-jobs + storedFiles
on success
- Mounted in components/chat/index.tsx as the first child of the main
content column so it sits above the chat title bar without taking space
when shouldPrompt is false (renders nothing)
- Reads aiAssistantName from Inertia page props so banner copy matches
the user's chosen assistant name
Stacks on feat/kb-policy-toggle (#894) because the policy KV mechanism
it writes through is introduced there. Both can land in rc.5; this PR
auto-rebases to rc once #894 merges.
Existing users on first upgrade to v1.32.0 will see this banner on first
chat visit post-upgrade — an explicit opt-in moment for content that was
already on disk. New users see it the first time they have curated
content downloaded.
…(PR #899 review) - KbPolicyPromptBanner: add onError toast to maybeLaterMutation so a failed policy save surfaces to the user instead of looking like a broken button (banner would otherwise reappear on next chat open with no explanation). - KbPolicyPromptBanner: set staleTime: Infinity on the prompt-state query. For users who already picked a policy (the vast majority), the result is effectively immutable per session — the mutations invalidate the key when it actually changes.
Adds an inline auto-index policy choice inside the Easy Setup wizard's existing AI section (Step 3 'Content', alongside AI model selection). The selection is persisted to KVStore['rag.defaultIngestPolicy'] on wizard submit — same key #894's KB modal toggle reads/writes — so a user who completes the wizard never sees the first-chat JIT prompt (#899); their decision is already recorded. Default is 'Always' so new users who keep the default get the 'just works' experience: content downloaded by the wizard becomes searchable as soon as it finishes embedding, without a follow-up step. Users who prefer the explicit-opt-in flow can flip to 'Manual' before submitting. Skipped when the user doesn't select the AI capability — the KV stays null and the JIT prompt handles the decision later if/when they enable AI from settings. UI placement - Step 3 'Content': new section below AI Models grid (only when AI is selected), two-button radio matching #894's KB-modal toggle pattern for visual consistency - Step 4 'Review': new 'Auto-index Setting' card summarizing the choice in plain English ('New content will be indexed automatically' vs 'New content will wait for you to opt in') so the user knows what they're agreeing to before clicking Complete Setup handleFinish - New api.updateSetting('rag.defaultIngestPolicy', ingestPolicy) call runs first, before service installs/downloads, so any content that finishes embedding during this same wizard run sees the right policy - Wrapped in its own try/catch so a transient KV write failure doesn't abort the rest of the wizard Stacks on feat/kb-policy-toggle (#894) — uses the policy KV mechanism that PR introduces. Auto-rebases to rc when #894 merges. Pairs with #899 (JIT prompt): wizard users decide here; non-wizard users decide at first chat. Together they cover every entry path to v1.32.0 without double-prompting.
One-time confirmation step gating bulk indexing actions that would consume a substantial amount of disk for embedding storage. Fires only when the user has policy=Always (i.e., the system would auto-index) AND the estimate trips either: - GUARDRAIL_ABSOLUTE_BYTES = 50 GB embedding cost, OR - GUARDRAIL_FREE_DISK_RATIO = 10% of current free disk space Under policy=Manual the guardrail is silent because the user has already opted out of automatic ingestion — the files would just queue as pending_decision either way. Pieces - inertia/lib/kb_guardrail.ts: pure decision helper with two constants and an evaluateGuardrail() that returns a verdict + reasons. No I/O on the helper itself so the logic is trivially testable - inertia/components/KbGuardrailModal.tsx: confirmation dialog. Headless UI Transition + Dialog, amber 'large operation' header, plain-English estimate summary, [Cancel] / [Proceed anyway] footer. z-[60] so it layers above the tier modal underneath instead of replacing it - inertia/components/TierSelectionModal.tsx integration: handleSubmit now evaluates the guardrail when policy=Always and embedEstimate is available; if it trips, we stash the verdict in state and render the guardrail modal as an overlay. Confirm runs finalizeSubmit (which is the pre-existing onSelectTier + onClose path); Cancel just closes the guardrail and leaves the tier modal as-is so the user can change their tier choice or flip the policy The disk-free signal comes from the existing useSystemInfo hook + getPrimaryDiskInfo helper. Passing freeBytes=0 (unknown) skips the relative-disk check, so the modal still works on hosts whose disk introspection failed — just relies on the absolute 50 GB threshold Tests - 9 cases in tests/unit/kb_guardrail.spec.ts: standard small batch (no trip), exact absolute threshold trips, over-absolute trips, over 10% free trips, both-at-once trips with two reasons, freeBytes=0 skip, freeBytes=0 + over-absolute trip, exact-10% boundary trips, just- under-both safe. All green. Stacks on feat/kb-tier-estimate-on-disk (#897) — consumes that PR's estimate endpoint to compute the verdict input. Auto-rebases to rc when #897 merges. Pairs with #894 (policy toggle) and #899 (JIT prompt): together the three PRs cover the 'how do I avoid surprising the user with auto- indexing they didn't ask for?' arc. Out of scope (deferred) - 6 hr time threshold (RFC §7): needs a per-host chunks-per-second metric we don't capture yet; would be a follow-up after Phase 4 self-calibration (RFC §15) lands - Wider integration (KbPolicyPromptBanner 'Index now' button, manual KB-modal sync): TierSelectionModal is the dominant bulk-decision surface and the right place to land this first
#901 review) - Disable TierSelectionModal Submit while the embed-estimate query is in flight, so a fast click can't slip past the guardrail with an undefined estimate. - Move KbGuardrailModal out of the outer <Transition> and render it as a Fragment sibling — Headless UI's Transition expects Transition.Child descendants, not raw conditional siblings.
The Stored Knowledge Base Files render crashed on first open in v1.32.0-rc.5 with `ReferenceError: sourceToDisplayName is not defined`. The table column's render() called `sourceToDisplayName(record.source)` but the function was extracted to `lib/kb_file_grouping.ts` in PR #892 and never imported in KnowledgeBaseModal.tsx. The unhandled error unmounts the entire React tree, so users see a blank screen ~20s after opening the panel. Root cause: PR #895 (conditional warnings) rewrote the render() and used `sourceToDisplayName(record.source)` instead of `record.displayName`, which KbFileGroup already carries from groupAndSortKbFiles(). PR #895's review follow-up (cbae48a) compounded this by narrowing the StyledTable generic from `KbFileGroup` to `{source: string}`, hiding the type drift from tsc. This restores the post-#892 pattern: - StyledTable generic back to `KbFileGroup` - Render uses `record.displayName` (works for both per-file rows and the collapsed admin-docs row; calling sourceToDisplayName on the synthetic `__admin_docs_group__` would have rendered that literal as the row name). Also folds in tooltip copy on the three bulk-action buttons (Reset & Rebuild, Re-embed All, Sync Storage) so the difference in destructiveness is visible on hover. Uses native `title` attribute via StyledButton's prop pass-through; no new component dependency. Inertia tsconfig catches this regression cleanly (TS2304 + TS2339); the pre-push hook only runs the backend tsconfig which excludes inertia/**, so the bug shipped. Tracking the typecheck-coverage gap as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Easy Setup wizard previously bundled AI model selection + the new ingest-policy radio into Step 3 alongside Wikipedia/ZIM tiers and curated content. Three problems with that: 1. Predicate divergence: "is AI selected?" was answered three different ways across Step 3 radio, Step 4 review card, and handleFinish persistence. Surfaced in @jakeaturner's review of PR #900. The three predicates disagree in real cases (e.g. Ollama already installed but user didn't re-select any models -- handleFinish writes the ingest KV while the review hides the AI summary). 2. Step 3 was overloaded -- ZIM tiers + curated content + AI models + ingest policy in one screen. 3. No way to opt out of seeing the AI policy radio when AI isn't part of the user's setup. This restructure makes step 4 a dedicated, conditional AI step: Step 1 (Apps) -- unchanged (services + remote Ollama toggle/URL) Step 2 (Maps) -- unchanged Step 3 (Content) -- Wikipedia + curated tiers only Step 4 (AI) -- NEW, conditional: model picker (or remote notice) + auto-index policy radio. Skipped entirely when AI isn't in the setup. Step 5 (Review) -- summary, reads back step 4's output via the same canonical predicate Decisions per issue #905 discussion: - Canonical predicate `isAiInSetup` as a useMemo. Single source consumed by step indicator, nav skip logic, review summary, and handleFinish. Both prior divergence cases collapse. - Step indicator renders dynamically: 4 dots when AI is off (positional display numbers 1..4), 5 dots when AI is on. WizardStep semantic values (1=Apps, 2=Maps, 3=Content, 4=AI, 5=Review) stay stable so nav handlers don't have to translate; the dot's `displayNumber` is decoupled from its `step` so users see sequential 1..N with no gap. - handleNext / handleBack are symmetric: 3 -> 5 forward, 5 -> 3 back, when !isAiInSetup. Same predicate gate. - Toggling AI capability off in Step 1 after AI step selections were made fires a confirm dialog ("Turning off AI will discard your AI model picks, indexing policy, and remote Ollama configuration") and clears selectedAiModels / ingestPolicy / remoteOllamaEnabled on confirm. Silent clear when nothing was set. - Remote Ollama toggle stays in Step 1 alongside the capability card. Don't fragment "am I using remote AI?" across two steps. The bundled review summary (renderStep5, was renderStep4) now uses `isAiInSetup` for the auto-index card visibility instead of the divergent `(selectedAiModels.length > 0 || remoteOllamaEnabled)`. Inertia tsconfig clean for this file (the only outstanding errors are the 3 KnowledgeBaseModal ones from issue tracked in PR #907 and the ~64 pre-existing errors elsewhere). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#883 §5) Closes the Manual-mode UX dead-end: after toggling 'Auto-index new content for AI?' to Manual, a freshly-downloaded ZIM (or any pending_decision file) had no UI path to opt in for embedding short of the global Sync Storage / Re-embed All bulk actions. Per RFC #883 §5, each Stored Files row now carries a state pill and an adaptive single-button action. State pill (left of any existing warning chips): - 'Indexed' — green; row had chunks in Qdrant or state row is 'indexed' - 'Not Indexed' — neutral; state is pending_decision or browse_only - 'Failed' — red - 'Stalled' — amber - admin_docs collapsed row has no pill ('Managed by NOMAD' carries it) Adaptive action button (paired with the existing Delete button per row): - pending_decision → 'Index' (force=false) - browse_only → 'Index' (force=true) - failed / stalled → 'Retry' (force=true) - indexed + warning chip → 'Re-embed' (force=true; confirm modal first) - indexed healthy / null → no action button (bulk Re-embed All covers it) Backend: GET /api/rag/files now returns { files: Array<{ source, state, chunksEmbedded }> } instead of a flat string[]. State + chunk-count come from a single KbIngestState query unioned into the existing Qdrant-derived source list (no new round trips). New POST /api/rag/files/embed validates the source is known, refuses if any inflight job already targets the same filePath (prevents double-click duplicate-chunk hazard), pre-deletes Qdrant points when force=true, then dispatches via the existing _dispatchEmbedJobsFor helper used by reembedAll. Per-file Re-embed (force=true on an already-indexed file) routes through a StyledModal confirmation since it deletes existing vectors before queueing a fresh job — same destructive-action weight as Delete's inline confirm but heavier since it affects search until the rebuild finishes. Folds in PR #907's blank-screen fix because my new render needs the same generic restored: `<StyledTable<KbFileGroup>>` and `record.displayName` (instead of the unresolved `sourceToDisplayName(record.source)` that ships in rc.5 and ReferenceErrors on modal open). PR #907 also adds title tooltips on the three bulk-action buttons; those tooltips are NOT included here — let PR #907 land first or independently for that part. Multi-select bulk-opt-in deferred per discussion: most Manual-mode users ingest 1-2 files at a time, the existing global toggle covers the bulk case, and checkboxes would expand scope past what rc.6 should hold. Will file a follow-up issue for an 'Index N pending files' single-click button once this lands. Tests-in-PR scope was limited to keeping `kb_file_grouping.spec.ts` green after the StoredFileInfo[] signature change (added asInfos() wrapper). Dedicated unit tests for embedSingleFile (unknown source / inflight refused / force=true delete-then-dispatch) and the new state-pill rendering will land in a follow-up PR alongside Playwright coverage of the row actions. Verification path: NOMAD3 currently runs project-nomad-admin:integration- rc6-preview (PRs #907 + #908 atop rc.5). After this branch is built into a new integration tag, I'll re-run targeted Playwright UAT on the KB modal covering: state pill rendering per state, Index click on pending_decision opts in cleanly, Retry on failed re-dispatches successfully, Re-embed confirmation modal copy + delete-then-dispatch on the military-medicine partial-stall row, and Delete flow untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… status Return a discriminated `EmbedSingleFileResult` from `RagService.embedSingleFile` with `code: 'not_found' | 'inflight' | 'delete_failed' | 'dispatch_failed'` on failure. `RagController.embedFile` now maps those codes to the correct status instead of collapsing every failure to 409: - not_found → 404 - inflight → 409 - delete_failed → 500 - dispatch_failed → 500 The `code` is also included in the JSON body so clients can branch without string-matching `error`.
Surfaces NOMAD's previously-silent model-stacking behavior and enforces a
"one chat model in VRAM at a time" invariant (the embedding model is
always exempt). Addresses Chris's NOMAD3 testing observation that
switching the dropdown in the chat header was invisibly slow on low-VRAM
hardware because the prior model was never unloaded — Ollama would
either evict it under memory pressure or load the new one on CPU after
the runner choked.
Three integration points all funnel through one new helper:
- **User changes the model dropdown** in an active chat session →
confirm modal "Switch to {newModel}? Switching to {newModel} will
start a new chat. Your current conversation stays available in the
sidebar." On confirm, fire `keep_alive: 0` against the previous chat
model, clear active session, set the new selection. Cancel snaps the
visible dropdown back to the previous value (no popup state leaks
into `selectedModel`).
- **User clicks a session in the sidebar** → no popup (system-initiated).
Restore the session's stored model into the dropdown and fire
`unloadChatModels(targetModel)` so anything that isn't the target
gets the unload hint.
- **Chat page first mount** → page-load normalization. Anything stacked
from a prior session gets the unload hint with the current selected
model as the target-to-preserve. Guarded by a ref so it only fires
once per page lifetime; gated on `selectedModel` being populated.
Backend surface is a single new helper and a single new route:
`OllamaService.unloadAllChatModelsExcept(targetModel: string | null)`
→ queries `/api/ps`, filters out (a) the embedding model name
(hardcoded `nomic-embed-text:v1.5` to avoid the RagService circular
import) and (b) `targetModel`, fires `POST /api/generate` with empty
prompt + `keep_alive: 0` in parallel against everything else.
Returns the names that were hinted. Best-effort: network or Ollama
errors are logged and swallowed so callers don't fail on housekeeping.
`POST /api/ollama/unload-chat-models` → thin wrapper validating
`{ targetModel?: string | null }`.
Why `keep_alive: 0` is safe against in-flight inference: per Ollama's
scheduler semantics, the hint sets the post-completion eviction timer
to zero — the runner is not terminated. If Session A is mid-response
on gemma when Session B fires the unload, gemma stays resident until
A's request completes, then evicts. The user-visible worst case is the
race where A's longer-running request re-extends the timer back to the
default and the unload is no-op'd; the next transition (or page reload)
gets another chance, and Ollama's own LRU catches up under memory
pressure regardless. Robust in-flight tracking deferred to a follow-up
if we see stale-state in the wild.
Base `rc`: v1.40.0 will inherit everything from rc.6 via the backmerge.
Frontend tests deferred to a follow-up PR; existing inertia tsconfig
errors are pre-existing and unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lift the hardcoded 'nomic-embed-text:v1.5' string out of both RagService and OllamaService into a shared EMBEDDING_MODEL_NAME constant in constants/ollama.ts. The duplicate in OllamaService existed only to dodge a circular import with RagService; the constants module has no service imports, so a shared constant eliminates both the duplication and the drift risk called out in the inline "keep in sync" comment.
Two related fixes surfaced by armandoescalante in #915 when clicking a Content Explorer category card (e.g. Medicine) on v1.32.0-rc.6: 1. TierSelectionModal placed a useMemo for freeBytes *after* the `if (!category) return null` early return (introduced in PR #901's guardrail integration). When `category` transitioned from null to non-null on first open, React saw a different hook count between renders and crashed the entire component tree with "Rendered more hooks than during the previous render", blanking the modal. Moved the freeBytes useMemo above the early return so hook order is constant. 2. `IconLibrary` was used as the icon prop on the Manage Custom Libraries button in remote-explorer.tsx but never registered in the DynamicIcon allowlist at admin/inertia/lib/icons.ts. Added it to both the import block and the icons map so the warning stops firing and the icon renders. Closes #915.
Since PR #36b6d8e moved tier-installation tracking from a client-side persistence model to a server-side derive-from-disk model, the card display only ever updates once every file in a tier is fully on disk. A user who picks Standard sees a blank card for the duration of the download (often hours for large tiers like Wikibooks). Worse, if some files finish before others, the card briefly shows a lower tier (e.g. Essential) before promoting to the selected tier on completion, which reads as "the system didn't accept my pick." Backend: compute a sibling `downloadingTierSlug` by unioning installed resource IDs with the IDs from active RunDownloadJob queue entries (waiting + active + delayed, failed deliberately excluded), then resolving the highest tier whose every resource is in that union. Set only when it differs from `installedTierSlug` — no point reporting "downloading Standard" when Standard is already fully installed. Frontend: unify the prominent corner badge logic in CategoryCard to a single `badgeTier` derived from selectedTier > downloadingTier > installedTier. Spinner + "(downloading)" suffix when in flight, checkmark for installed/selected. The pill row and lime border follow the same source. Verified on NOMAD3: backend correctly resolves the downloading tier from in-flight BullMQ jobs; CategoryCard shows the spinner badge immediately on Submit and switches to the checkmark variant when downloads complete. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RunDownloadJob's onComplete handler was unconditionally firing EmbedFileJob.dispatch after every ZIM download, gated only by "is Ollama installed?". The rag.defaultIngestPolicy KV setting was never consulted, so users who explicitly set Auto-index to Manual still got every newly-downloaded ZIM auto-embedded. RagService.scanAndSync already handles Manual correctly by recording pending_decision rows instead of dispatching (rag_service.ts:1587-1638 via decideScanAction). The post-download path skipped that gate. Mirror the same check at the dispatch site: read the policy KV; if Manual, firstOrCreate a pending_decision row in kb_ingest_state so the per-file Index affordance from PR #909 surfaces the file the same way scan-time-discovered Manual files do. firstOrCreate (not create) so a re-download doesn't demote an existing indexed/failed row — the user can explicitly re-index from the KB panel if they want fresh content. Verified on NOMAD3: with rag.defaultIngestPolicy='Manual', every ZIM downloaded today via Content Explorer (agriculture-essential + computing-essential, ~62 MB across 7 files) wrote kb_ingest_state rows with state='indexed' instead of pending_decision. Real bug, not a hot-patch artifact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…921) foreignKey/localKey were swapped on the ChatMessage → ChatSession relation. Per Lucid's belongsTo contract, foreignKey is the column on the child model and localKey is on the parent — so this must be { foreignKey: 'session_id', localKey: 'id' }, mirroring the inverse hasMany on ChatSession. The relation is not currently preloaded anywhere in the codebase, so no runtime behavior changes today; this closes a latent bug that would have broken any future preload('session') call.
…bypass Replace literal string matching with ipaddr.js parsing so equivalent encodings of 169.254.169.254 (::ffff:169.254.169.254, ::ffff:a9fe:a9fe,fully-expanded forms) and fd00:ec2::254 are all rejected.
Bumps [@adonisjs/tsconfig](https://github.com/adonisjs/tsconfig) from 1.4.1 to 2.0.0. - [Release notes](https://github.com/adonisjs/tsconfig/releases) - [Commits](adonisjs/tsconfig@v1.4.1...v2.0.0) --- updated-dependencies: - dependency-name: "@adonisjs/tsconfig" dependency-version: 2.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
c9389ee to
ba2b44c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps @adonisjs/tsconfig from 1.4.1 to 2.0.0.
Release notes
Sourced from @adonisjs/tsconfig's releases.
... (truncated)
Commits
10e1368chore(release): 2.0.00a98126feat: use Node 24b2e14dbchore: publish under latest tag0e002c9chore(release): 2.0.0-next.320a1889feat: update glob to work with hot-hook703e91cchore(release): 2.0.0-next.2089e441fix: use configDir variable to resolve paths from the host app910dc54chore(release): 2.0.0-next.18b192ebfeat: update includes and excludesca2f85aci: remove major release tagMaintainer changes
This version was pushed to npm by GitHub Actions, a new releaser for
@adonisjs/tsconfigsince your current version.