docs / history / CHANGELOG_v2.md
docs / history / CHANGELOG_v2.md
โ ๏ธ Superseded by
CHANGELOG_v3.mdon 20.09.2026 (file-size rotation, same house pattern as the v1 โ v2 hand-off). This file is preserved as history โ no new entries are added here; the newest 8 entries below were moved verbatim intoCHANGELOG_v3.mdat rotation time. Older entries ([12.09 READS-EXTENSION]and earlier, plus the mid-file release-history paragraph) remain in this file unchanged.
| Entry | Date |
|---|---|
| SPEC-C (memory-store data-loss arc; dual-writer interleave root fix) | 20.09 |
| F4: CWD state file relocated to persistent data dir | 19.09 ~14:3x |
| Cluster-aware tool ordering wired into toolsProvider | 18.09 ~15:5x |
| AutoTracker F1+F2: mid-loop checkpoint hysteresis + pre-compression notice | 17.09 ~18:4x |
| DE-STRAngle: pattern_scan wall-clock cap removed; deterministic size bounds | 16.-17.09 ~19:3x |
| DRAIN-GRACE: worker-pool thrash fix (delayed idle-drain, 500 ms) | 15.09 ~19:0x |
| SEARCH-NORM: search case- AND word-separator-insensitive | 15.09 ~17:4x |
| RIPGREP TOOL SWAP: grep_files removed โ standalone ripgrep tool | 14.09 ~18:0x |
manage_projects absorbs registry reads (info / list / search)Context: same-evening follow-up to the MANAGE remodel โ user asked which further tools could be merged into a unified action tool; candidate analysis by entity-lifecycle + parameter-overlap criteria picked extending manage_projects with read-only actions as the top fit (same entity ProjectRegistryData, same file, zero new state).
Changes (src/tools/contextManagementTools.ts):
Tests: new manager-level suite block in tests/manageProjects.test.ts ("READS-EXTENSION", 9 tests): getProjectByPath known/unknown (pins the info not-found branch), getAllProjects freshโ[] + round-trip, search name-substring (case-insensitive) / path-only branch / maxResults cap / no-match โ [] with has_projects status vs fresh registry missing status (F1' gate inputs pinned). F1' status semantics themselves remain pinned in projectRegistryStatusMigration.test.ts โ deliberately not re-asserted. No existing test pins the tool-level response shapes of the absorbed tools (scan-verified) โ alias shape-preservation is by construction; expect the prior green baseline + 9 new cases, zero churn elsewhere.
Verification: โ
CLOSED 12.09 ~23:29 โ user confirmed "all tests PASS" (typecheck + jest green; one syntax error in the NEW test block itself was caught on first run and fixed pre-confirmation: F1' apostrophe inside single-quoted test titles โ reworded to F1-prime; zero product-code impact). No version bump โ folds into the pending v1.9.18 proposal (rev 30) alongside MANAGE + FIX #32; release decision remains with user. Adds to the release .bak sweep: src/tools/contextManagementTools.ts.bak, src/locales/{en,de,es,zh-CN,zh-TW}.ts.bak, tests/manageProjects.test.ts.bak.
updateSessionIndex silent skip for orphaned per-copy legacy index (recurring log-noise class)Context: user spotted a recurring [ERROR] line in LM Studio's logs at 12.09 ~22:22 ([StateManager.updateSessionIndex] Project 'ai_toolbox' is not yet registered in indexโฆ). Forensics (all live-verified same session): updateSessionIndex() โ invoked after EVERY state save โ requires an entry for the current project in <PLUGIN_ROOT>/.session_index.json, a PER-COPY file next to the running bundle. The install dir holds no such file, and since REG-MOVE no code path seeds one anymore: registration lives exclusively in the persistent data-dir registry (~\.lmstudio\extensions\data\crunch3r\ai-toolbox\project_registry.json). Consequence: on every fresh copy (i.e., after each lms dev --install wipe) the warn fired on every save_memory/save_session_summary, and its suggested remedy was unperformable โ manage_projects(action='register') never writes to that file. Zero data impact: memory + summaries persist via working-dir msgpack stores, sessions.json index unaffected; only the legacy last_session_saved bookkeeping in the orphaned file was skipped (its sole consumer is ProjectRegistryManager's READ-ONLY migration fallback _loadFromSessionIndex). Side finding: the 22:22 line predates a 22:23:23 reinstall (install-state.json at=1789244603563), so it came from a stale in-memory bundle (old wording); on-disk already carried today's code.
Change (src/stateManager.ts, else-branch of updateSessionIndex(), ~10 lines): warn โ silent return with a FIX #32 comment documenting the orphaned-by-design state and pointing at Option C (full mechanism removal) as tracked follow-up. Registered-entry behavior unchanged (timestamp update + registeredโactive promotion).
Tests: no test pinned the old warn (scan-verified across tests/); existing session-index suites cover the registry's read-only legacy fallback, which is untouched โ expected to stay 779/49 green with zero test changes.
Verification: โณ user-run: npm run typecheck + targeted npx jest tests/stateManager.test.ts tests/manageProjects.test.ts --silent (or full npm test, expect 779/779 ยท 49/49). Live proof after sync+restart: any save_memory in a live chat โ NO [StateManager.updateSessionIndex] line in main.log.
Versioning: no bump โ folds into the pending v1.9.18 proposal (rev 30) alongside MANAGE; release decision remains with user.
register_project โ manage_projects, with unregister/update/clear_all + tombstone protectionContext: user request "register project" surfaced that the registry had no way to REMOVE a project once registered, and stale cross-stamped session-memory entries could silently resurrect unregistered paths via _syncFromSessionMemory(). Design decision (user-approved Option B): remodel register_project into one mutation tool manage_projects with four actions; register_project stays as a deprecated alias for one release cycle.
Changes:
Tests: new suite tests/manageProjects.test.ts (7 tests: unregister+tombstone, no-op for unknown path, re-registration clears tombstone, update rename/sourceDirs without creation, update no-creation contract, clearAll preserves tombstones, resurrection-block test with a control proving the sync path is exercised). One pre-existing assertion updated (projectRegistryStatusMigration.test.ts F1' note now routes to manage_projects).
Verification: โ
Full npm test green 12.09 ~22:15 โ 779/779 tests, 49/49 suites. During this run one fixture defect surfaced in the new suite itself (implementation was correct): the resurrection-block cross-stamp memory file had been written as raw JSON bytes into a .ai_toolbox_memory.msgpack that _syncFromSessionMemory() decodes via @msgpack/msgpack โ first byte 0x5B decoded as fixint โ non-array โ source skipped silently, so the "ghost NOT resurrected" assertion passed VACUOUSLY while the no-tombstone control (which must pass to prove the sync path is live) failed. Fix: fixture now written with encode() from the same library (tests/manageProjects.test.ts); post-fix the tombstone logic is genuinely exercised on both sides. โ
User-run gates all green by ~22:17: npm test 779/779 ยท 49/49 (22:15) + npm run typecheck clean (analyzer tsc integration inert here โ filesChecked 0, hence user-run). All verification gates closed. No version bump yet โ release decision pending (proposal on table: v1.9.18 / rev 30 per house convention).
Context: 11.09 reinstall forensics proved lms dev --install wipes the plugin dir โ including BOTH registry sources (project_registry.json + .session_index.json) โ while .ai_toolbox_state.json can still hold a VALID workingDir. Two live defects followed: (a) post-reinstall, list_projects()/search_projects() returned [] for the whole session although durable session-memory stamps existed; (b) test runs leaked fake tmp working dirs into <repoRoot>/.ai_toolbox_state.json, which lms dev --install shipped into the plugin snapshot and broke boot CWD resolution live (canary d).
Changes:
Verification: user-run full npm test 772 passed / 48 suites at 20:21 and again 21:01 after FIX #31b. Anti-leak verified without re-running (delegated-proof): dev .ai_toolbox_state.json content = valid dev path + mtime 18:42Z (pre-test-run) โ untouched during the green run. Deployed live ~20:35; canary a โ (data-dir registry primary+.bak written by the live process, content verified), b unreachable by design this cycle, c code-reviewed only.
Versioning: no bump โ v1.9.17 rev 29 stays current; package.json description updated to "772 passing tests across 48 suites". Canary d โ closed 12.09 ~21:2x: plain LM Studio restart โ fresh chat WITHOUT cd โ tool-process CWD already resolved to the dev tree before any explicit pin (previous_directory = dev path at session start), relative ops landed in the dev tree. All four canaries now closed (a โ live-verified, b unreachable by design this cycle, c code-reviewed, d โ).
Context: LM Studio's getPluginConfig() is PER-CHAT scoped and falls back to schematic defaults for values the host has not explicitly persisted, so a toggle flipped in one chat could reset in the next โ user tool choices had no plugin-side memory. Agreed design (08.09): auto-capture + sparse storage + conservative "sticky keys" contract; booleans only (never paths/tokens/secrets).
Changes:
Verification: all four gates green (user-run 08.09): typecheck 0 errors ยท ESLint 0 warnings ยท targeted jest both suites green ยท full npm test 761/761 PASS. Live proof: installed source-run plugin captured real user toggles into %USERPROFILE%\.ai_toolbox\tool_gating_profile.json (browserAutomation, executionShell, executionTests = true, written 08.09 ~21:28 local).
Versioning: released as v1.9.17 โ package.json v1.9.16 โ v1.9.17 (description "761 passing tests across 46 suites"); manifest.json revision 28 โ 29 so LM Studio detects the update; installed-copy metadata mirrored same session (live install runs from source, verified byte-identical to dev pre-bump). Pending: re-compress tests.7z before Hub publish (lms push) โ tests/ changed since last archive.
web_search zero-result fallback fix (dead engine no longer stops the chain)Context: live network probe (08.09, datacenter IP) showed ddg-api blocked by DDG anomaly detection and Google returning an HTTP-200 JS-shell/consent page with zero parseable results. The old searchWithFallbackChain treated a 0-result engine response as success (success:true, count:0) and STOPPED the chain there โ Bing (verified working from this IP, 11 results) was never reached.
Change (src/tools/webResearchTools.ts, minimal):
Post-install verification (same day, this session): rebuild + reinstall ~17:28โ17:30 (install-state at=today 17:29:50; shipped bundle .lmstudio/production.js contains all fix markers ร1). Live test web_search("Node.js latest LTS version") โ success, count=10, engine=ddg-fetch โ log line L3268 @17:36:28 shows Search engine "ddg-api" failed: DDG detected an anomalyโฆ followed by correct chain advance (today the dead first engine took the hard-fail path; the zero-result soft-fail line is covered by the regression tests + bundle markers). Zero worker-pool rollback-trigger lines post-restart.
Versioning: released as v1.9.16 โ package.json bumped v1.9.15 โ v1.9.16 (description: "748 passing tests across 45 suites") and manifest.json revision advanced 27 โ 28 so LM Studio detects the update; reinstall + restart completed same day, fix verified live above.
ripgrep promoted to runtime dependency for Hub installsContext: post-rev-26 live verification on the user's machine (03.09) confirmed B' phase-1 LIVE locally โ then exposed a packaging gap for everyone else. The ripgrep fast path (pattern_scan B' + grep_files rg engine) resolves the npm package ripgrep via lazy dynamic import at tool-call time, but it was declared in devDependencies. Official LM Studio docs (verified 03.09): a Hub install auto-downloads dependencies from package.json+package-lock.json with bundled Node v22.21.1 and never runs postinstall scripts โ so any production-scoped (--omit=dev) install would omit the dev-flagged package โ first engine call resolves {status:'fallback-required', reason:'missing-dependency'} โ every Hub user silently pinned to pure-JS fallback forever (stdout invisible in main.log, no error surface). Local dev installs mask this because a full npm install populates devDeps too.
Change (commit ec783a8): "ripgrep": "^0.3.1" moved devDependencies โ dependencies; lock entry loses its "dev": true flag โ pin stays exactly 0.3.1 (the build live-verified this session; no version chase mid-release). Same commit's npm normalization also synced the stale lock root version (v1.9.12 โ v1.9.15, stale since C3) and applied within-range transitive patch bumps only (browserslist chain [dev] + @xmldom/xmldom 0.8.13โ0.8.15).
Verification: lock diff audited = exactly the planned flips; smoke suites after refresh: ripgrepEngine.test.ts + patternScanBPrime.test.ts 39/39 PASS incl. real-WASM integration (resolution unaffected). End-user proof only exists on a clean Hub install โ one-line check when testable: does node_modules/ripgrep/ exist after first download?
Versioning: no version-number change โ v1.9.15 stays current; manifest.json revision advanced 26 โ 27 so LM Studio detects the update (pure rev-bump precedent: v1.9.10 rev 20โ21, 28.08). Re-publish via lms push.
pattern_scan ripgrep phase-1 candidate prefilter (B')Context: plan_1788368034162_2jkd1nvxv. B' ports the grep_files v1.9.13 Option A architecture verbatim to pattern_scan: regex-mode directory scans first run an in-process WASM ripgrep prefilter that names the files whose content can match; ALL line shaping and every resource gate stay in the existing JS worker pipeline, so any non-ok engine outcome leaves output byte-identical to the pre-B' behavior. Goal: cut per-line .test() CPU cost (and ReDoS exposure) on regex-mode scans without changing any visible output field, cap, or skip-record contract.
Changes:
Session triage (02.09, two new-feature defects fixed during verification):
let effectiveMaxDepth = SCAN_DEFAULTS.maxDepth; carried a narrowed literal type (10) from the frozen defaults object โ TS2322 at the dir-branch reassignment. Fixed with an explicit number annotation (type-level only, zero runtime effect).pre-B' inside a single-quoted string) terminated the literal early โ 7-cascade TS1005/TS1128. Fixed by escaping to , matching the file's own convention.Verification (user-run, 02.09): typecheck / lint / build all PASS; full Jest suite ALL PASS GREEN. Both engine regimes were empirically exercised on the dev machine: a targeted two-file run downgraded under jest (ESM import) โ byte-exact fallback path proven (46/46); the subsequent FULL-suite run proved phase-1 LIVE in the same environment โ the pre-analyzed binary-test failure appeared exactly once with precisely the documented divergence, confirming live behavior before the tolerant rewrite. Host-runtime performance follows the grep_files precedent (3.4x/2.8x on hosts b/c/d/e); per pinned observability facts, post-install engine state is verified via tool-response fields (stats.filesScanned differential), since plugin stdout never reaches main.log.
Versioning: released as v1.9.15 โ package.json + manifest.json bumped v1.9.14 โ v1.9.15 on 02.09, revision advanced 25 โ 26 so LM Studio detects the update (user-directed). Suggested commit: pattern_scan B': ripgrep phase-1 candidate prefilter (Option A parity, fallback-guaranteed).
get_memory local-file parse guard (hotfix)Context: post-v1.9.13 reinstall verification surfaced a pre-existing defect in get_memory's PRIORITY-1 read path (src/tools/contextManagementTools.ts). Every call logged to plugin stderr: [ContextManagement.get_memory] Local file parse failed: TypeError: Cannot read properties of undefined (reading 'startsWith'). Falling back. โ first observed 02.09 ~17:43, reproducing on the fresh v1.9.13 process at ~18:07 after reinstall.
Root cause: .session_context/.ai_toolbox_memory.msgpack in a working dir is shared storage for two record families: save_memory facts ({key,value,timestamp}) and auto-context entries from the context-management/auto-summarize stream ({id,title,type,content,tags,scope,frequency,project_path,timestamp[,date]}). This project's file held 154 records โ only 3 with a string key; the other 151 have none. The reader filtered with e.key.startsWith('memory_') โ TypeError on the first keyless record โ the whole try-block aborted and PRIORITY-1 (local project file, the documented #1 source) silently died; every read fell through to plugin-root/RAM fallbacks. Writes were never affected (save_memory kept persisting); only reads degraded โ facts looked present in RAM but would not survive a process restart from disk.
Fix (2 lines, contextManagementTools.ts only): the key filter is now null-safe at both read sites โ local project file and plugin-root fallback:
No schema change, no migration, no new storage separation; context-shaped records are now skipped by the filter (their purpose) instead of throwing. The third startsWith('memory_') occurrence in the same function (over string keys from Object.keys) was already safe and is untouched.
Verification: static re-read of both patched lines + CRLF integrity audit (file remains 100% CRLF); user-run gate pending: npm run typecheck, npm run test:quiet, npm run build, then reinstall v1.9.14 โ get_memory expected to return all 3 local memory_* facts with no parse-failure line in main.log.
Versioning: released as v1.9.14 โ package.json + manifest.json bumped v1.9.13 โ v1.9.14 on 02.09, revision advanced 24 โ 25 so LM Studio detects the update (user-directed).
grep_files ripgrep-backed regex engine with JS fallback (Option A)Context: plan_1788282568340_z5a4r521c, gated end-to-end by P0 spike data (scripts/rg_spike.mjs, T1โT4 โ kept as the diagnostic/evidence generator) and a pre-change characterization battery. Goal: cut per-line CPU cost of regex-mode scans (and ReDoS exposure on the inline path) without changing any visible output field, hang guard, or skip-record contract.
Changes:
Verification (user-run, G9): four verification rounds โ round-1 triage โ fixes on disk โ round 2: two root causes found and minimally fixed:
Versioning: released as v1.9.13 โ package.json + manifest.json bumped v1.9.12 โ v1.9.13 on 02.09, revision advanced 23 โ 24 so LM Studio detects the update (user-directed). Suggested commit: feat(grep_files): ripgrep-backed regex engine with JS fallback.
Context: full dead-code audit of all 140 TS files in src/ + tests/ (AST unused-export detection cross-validated by import-graph reconstruction). Tier-1 = confirmed orphans with zero referencers anywhere. Removal executed stepwise per user constraint (โค5 destructive ops/phase, checkpoint after each phase; plan_1788278947185).
Deleted โ 13 files (~90.3 KB / ~1739 LOC):
Behavior-neutral by construction: tsup entry is src/index.ts only โ none of these modules were ever in the shipped bundle. Zero config edits (jest.config.cjs re-read in full: live ./tools/utilityTools.js mock mapper intact; no entry referenced a deleted path).
Evidence-gate lesson (no blind deletion): audit initially flagged tests/grep_files.test.ts as importing nonexistent ../src/utils/helper. Full-file read proved the string exists only inside test-fixture template literals (fs.writeFile fixture content) โ an import-graph false positive from matching file text, not real imports. Gate held: green baseline + clean imports โ test kept.
Verification (user-executed): BEFORE โ typecheck 0 errors / Jest all-green / build success; AFTER (all 13 deletions) โ identical results (npm run typecheck, npm run test:quiet, npm run build). No suite added/removed/broken.
Docs sync (same session): current-state docs aligned with code โ ARCHITECTURE.md module tree + recode-rule sections, TOOLS_REFERENCE.md rule table; historical entries in CHANGELOG/RELEASE_NOTES left intact as history.
Versioning: no bump โ v1.9.12 rev 23 stays current. Folds into the pending user deploy (together with the executedTool transparency stamp from the entry below). Suggested commit: chore: remove audited Tier-1 dead code (~90 KB).
executedTool transparency stamp in the toolsProvider wrapper (silent-substitution incident follow-up, option B)Context: "silent tool substitution" incident (user's own LM Studio logs 2026-09-01, 16:18โ16:23): with grep_files disabled, a chart ran instead; after disabling generate_chart, run_python ran โ plus false success narration and stray PNGs. Attribution closed from log evidence: model/generation-side tool substitution + transcript observability gap โ NOT an ai_toolbox routing defect (host remapping unproven; a 16:20 tool-card screenshot would be the only remaining way to fully exclude it). The wrapper already logged the true executed name ([AutoTracker] [DELTA] โฆ from <name>), but that ground truth never reached the chat transcript.
Change (src/toolsProvider.ts, instrumentedImplementation): after the measurement/guard try-catch, plain-object results gain one additive field โ executedTool = the registered (minified) name of the implementation that actually executed: { ...result, executedTool }. Strictly additive:
Object.prototype) gain exactly ONE key. No tool emits this key today (grep-verified across src/ before introduction); if a collision ever appeared, the wrapper value is authoritative (spread order).Tests: new suite tests/executedToolTransparency.test.ts (8 tests) runs the REAL pipeline (registration โ minifyTools โ instrumentation wrapper) with six side-effect-free probe tools injected through one registry module โ mocked at the moduleNameMapper'd stub path (../__mocks__/markdownPreviewTools.js) so it intercepts the provider's actual dependency graph. Coverage: stamp === registered name; per-tool identity across probes in a single run; additive-only key contract (original fields verbatim, exactly one key added); byte-identical passthrough for string/number/array/null; FIX #20 A1 regression guard (recordToolResult once per success, zero on failure โ pre-existing semantics preserved); error propagation unchanged; godMode sweep (unique non-empty names + wrapper contract across every exposed tool).
Verification status:
{} / array / string / number / boolean / null / undefined / class instance / Buffer / Date / source non-mutation / collision precedence / unknown_tool fallback).npx jest tests/executedToolTransparency.test.ts, then full baseline npm test โ expect 657 existing + 8 new green. Sandbox cannot execute (child_process blocked by policy).Deployment: the live install runs from source (src/) โ sync src/toolsProvider.ts into the LM Studio install folder and fully restart LM Studio. No rebuild/reinstall required (bundles carry no version strings).
Versioning: no bump โ v1.9.12 rev 23 stays current. Candidate for a future release decision (v1.9.13 or fold-in); manifest untouched in this session.
pattern_scan tool + puppeteer connected property-read fix + dead-file removalChanges:
Versioning: released as v1.9.12 โ package.json + manifest.json bumped v1.9.11 โ v1.9.12 on 31.08 (user-directed); manifest revision advanced 22 โ 23 so LM Studio detects the update.
grep_files completion telemetry (live-verified same day; closed counter audit + exonerated plugin in LM Studio freeze incident)Change (src/tools/fileSystemTools.ts): two per-call wall-clock lines added to the grep scan, both via console.warn โ stderr โ the ONLY channel LM Studio persists to %APPDATA%\LM Studio\logs\main.log (stdout is dropped), accepted cost: one [error]-tagged line per grep call.
[grep_files] completed in <N>ms โ <filesScanned> file(s) scanned, <matches> match(es), <skipped> skipped (appends [ABORTED โ partial results] when the internal controller is set).[grep_files] aborted in <N>ms (host/timeout) โฆ [partial results].Why: log forensics should be able to separate scan time from model-generation time per call โ the "60 s+ grep waits" class of reports. Telemetry proved the engine fast (2โ17 ms observed in production on 30.08) and localized perceived wait to LLM generation around sub-tenth-of-a-second tool calls + LM Studio host-side cancel behavior.
Verification (same day, post-rebuild/reload): five consecutive production lines matched their JSON results exactly โ 4ms/8 files/20 matches ยท 2ms/0 scanned-1 skipped (correct: size-skipped file never reaches the search gate) ยท 3ms/1/14 ยท 17ms/77/0/2 skipped ยท 5ms/1/16. Full counter audit closed: filesScanned++ only after the size gate (~L2391) and line-cap gate (~L2452); skip-push sites L2376 (size) / L2444 (line cap) / L2469 (worker kill).
Incident context (same evening): an LM Studio 0.4.23 app freeze at ~17:49 (prediction-loop stall on tool dispatch; Canceling predictions timed out unhandled rejection + engine SIGTERM; recurring 08-26โ08-30) was root-caused as host-side โ the orphaned call left zero trace in the plugin, and every call that reached the plugin logged correctly. Upstream issue draft: LM_STUDIO_ISSUE_2026-08-30_prediction_loop_stall.md.
Problem (residual hang class after FIX-HANG-1..4): a single catastrophic-backtracking RegExp.test() on the main thread blocks the event loop and starves EVERY timer โ including the 15 s scan deadline, the 30 s fallback AND the 20 s wall-clock backstop. No cooperative gate can preempt synchronous JS; only another thread can.
Fixes (src/tools/fileSystemTools.ts):
Evidence: probe artifacts + results archived to docs/history/GATE_PROBE_EVIDENCE_fixhang5c.md and docs/history/FIXHANG5_REDOS_RESULTS.md (code comments reference the new locations; both files removed from disk 08.09 โ recover from git history, e.g. git show d319c92~1:docs/history/FIXHANG5_REDOS_RESULTS.md).
grep_files limits (docs-only, no code change)grep_files (code-verified + LIVE VERIFIED same day)Problem: prose alternations containing a bare & โ e.g. "Backup & Restore|Git & GitHub" over TOOLS_REFERENCE.md, which contains both phrases โ were silently forced into literal mode โ 0 matches, triggering LLM retry loops. Root cause pinned in isSafeRegex() (src/security.ts): the code-signature heuristic pair /([*+?&]/ + /[\w][*&]|[\*&]\s+\w/ fired on "& Restore" / "& GitHub" even though & is not a JS regex metacharacter (zero backtracking risk) โ i.e. a pure false positive.
Fixes:
Tests: 3 new regression specs in tests/security.test.ts covering the REV-24 decision boundary; full suite re-run green at 628/628.
Verification status:
tsc --noEmit OK ยท tsup build OK ยท typecheck OK ยท lint OK ยท jest .Deployment note: the live install runs from source (src/ via entry.ts, no dist/ in that folder) โ a direct src-file sync + full restart is sufficient; no rebuild/reinstall required. Lesson logged: patternMode:"literal" on a known-containing fixture โ suspect stale runtime first, compare installed-copy mtimes before re-opening the code.
Versioning (updated 28.08 ~20:45): shipped as part of v1.9.11 โ package.json + manifest.json bumped v1.9.10 โ v1.9.11 per user decision, superseding the 25.08 "no bump" policy; manifest revision field stands at 22. (No dist rebuild required for this hotfix itself โ installed copy runs from src/; baseline backup ai_toolbox-v1.9.11-release-baseline-2026-08-28.zip taken pre-cleanup.)
Problem: persistent-memory round-trips silently lost โ get_session_summary() / get_memory() returned empty despite all 50 sessions existing in .session_context/sessions.json. Root cause: StateManager fresh-start overwrote .ai_toolbox_memory.msgpack before/during initialization, plus two related races in the cache-rebuild and origin-tracking paths.
Fixes (src/stateManager.ts):
saveToFile() can run before readiness โ added ensureReady gating at the save site (~L345) so writes never race initialization.Tests: new deterministic regression suite tests/stateManagerRace.test.ts (5 tests) reproducing each race without timing luck; full Jest suite green pre-deploy, lint clean.
Build & deploy: rebuilt dist/ via tsup (worker-thread sandbox workaround for the no-shell session environment); bundle-verified B1/B2/B3 markers present in fresh build (ensureReady at save site, _rebuildKeysCache merge, conditional _origin). manifest.json revision 20 โ 21 (pre-bump state preserved as manifest.json.bak). User re-deployed ~16:34 same day.
Live verification: context write/read round-trip proven in the new process โ milestone entry persisted across restart, store diagnostic reports 1 record; no further memory-loss events reported.
Versioning: no version bump โ v1.9.10 stays current (maintainer policy); only manifest.json revision advanced to 21 so LM Studio reloads the plugin.
Problem: deterministic multi-day V8 heap OOM (Ineffective mark-compacts near heap limit) in the vector-RAG chunkers. Root cause proven by plain-Node repro: chunkText / chunkDocxText / chunkPdfText (src/tools/vectorRagTools.ts, ~L212/277/331) advanced with a fixed stride while the partial final chunk was shorter than the overlap budget โ for certain word-count remainders the window start reached a fixed point (startIndex === endIndex) and the loop never terminated (repro: len=34,209 words, size=500/overlap=50 โ stalls at start=lenโ50). Most real documents terminate "by luck"; poison remainders do not.
Fixes:
Verification:
tests/webResearchTools.test.ts and the new oversized-page regression spec.npm run build; bundles carry no version strings, so a normal rebuild suffices).Versioning: no version bump โ v1.9.10 stays current. This supersedes the "candidate for v1.9.11" notes in the three 24.08 entries below (maintainer decision 25.08: version number stays at 1.9.10).
Context: screenshot failure (Tool call failed โฆ Errors: WebSocket closed by the client on rag_web_content() + fetch_web_content(), Weinstein Wikipedia URL). Forensic verdict: that string exists NOWHERE in src/ (grep-verified) โ it is LM Studio's transport-level report for an in-flight tool when the plugin host process dies (same-day exit-134 heap-fatals). Both tools failed within ~5 s โ common cause = host, not two independent bugs. rag_web_content was still the worst transient allocator on web paths and guaranteed-to-fail on real Wikipedia articles (page > hard cap โ max allocation consumed, then success:false).
Changes:
Tests:
tests/vectorRagTools.ragWebContent.test.ts (4 tests + sanity): markup-stripping contract (bestMatch.text contains prose, no ), soft-cap contract (oversized stream โ , , stripped chunks), error contract preserved (), real- guard.Verification status:
filesChecked: 0) โ run locally: npm run typecheck, then npx jest tests/vectorRagTools.ragWebContent.test.ts tests/webResearchTools.test.ts --silent (or full npm test). Expect green; the two rag suites are the only behavior changes.success:true with readable chunks (page now truncates softly instead of failing), no "WebSocket closed by the client". If a host OOM recurs, the line names the next suspect.Known residuals (tracked, NOT changed): LocalVectorStore in vectorRagTools.ts grows unbounded with repeated rag_index_* calls (no size cap like the other LRU caches got) โ candidate for OOM part 3 if indexing-heavy sessions recur. GitHub-API .json() reads (gitGithubTools.ts) remain unbounded (small internal payloads, unchanged since part 2).
Versioning: no version bump โ folds into v1.9.11 together with the OOM parts 1+2; bump package.json + manifest.json at release time. Bundle check after rebuild: name:"rag_web_content" must remain exactly 1ร per dist file (dedup invariant since v1.9.10).
Problem: two more Ineffective mark-compacts near heap limit host kills today (~20:24 and ~21:10), both ~40 s after a fresh plugin start while web-search tools were in flight. The part-1 guard was live and working (both windows show its Page too large (> 48.8 KB streamed) size-cap error firing correctly) โ but it only covered fetch_web_content + rag_web_content.
Evidence from server log (2026-08-24.1.log) for the ~20:24 crash: Search engine "ddg-api" failed: DDG detected an anomalyโฆ โ fallback chain took over (unbounded .text() paths) โ death. The same correlation holds for ~21:10 (web_search in flight, no DELTA completion logged).
Fixes (all minimal, error contracts preserved):
Known residuals (tracked, NOT changed โ out of scope for this fix): gitGithubTools.ts GitHub-API .json() reads, lmStudioApi.ts local-API reads (small internal payloads), dead file tools/networkToolsRegistry.ts (still zero imports โ deletion remains backlog). The exact GB-scale allocator is not yet proven from static analysis alone; the watchdog exists precisely to identify it in one more crash if they recur.
Verification status:
npm run typecheck, npx jest tests/webResearchTools.test.ts --silent, then rebuild + reinstall, then live web-search test (user testing immediately).[HEAP-GUARD] line + the last [DELTA] lines in the server log identify the responsible tool without a debugger.Versioning: no version bump โ candidate for v1.9.11 alongside the part-1 OOM guard; bump package.json + manifest.json revision at release time.
Fixes a class of hard plugin-host crashes: FATAL ERROR: Ineffective mark-compacts near heap limit when fetching large page bodies.
Root causes (proven by code read, session 24.08.2026):
fetch_web_content: await response.text() materialized the ENTIRE body before its 50 KB check ran โ oversized pages allocated their full size regardless of the cap.rag_web_content (): raw unbounded , no cap at all, plus ~5โ10ร memory amplification in word-array chunking/embedding, and no timeout.Fixes:
Verification status:
Page too large (> 48.8 KB streamed) โ the bounded reader is active in the installed build and the host stayed alive (pre-fix wording would have been (177.2 KB) from full buffering).npm run typecheck + npx jest tests/webResearchTools.test.ts --silent to be run locally (session environment has no shell).Versioning: no version bump in this change โ candidate for v1.9.11; bump package.json + manifest.json revision at release time.
Out of scope (tracked): the three search-engine fallback functions still use raw .text() on known-small result pages; dead file tools/networkToolsRegistry.ts removal.
Fixes:
Versioning: package.json v1.9.10 + manifest.json revision 20. Bundles do not embed version strings (proven in the v1.9.9 release), so a normal rebuild suffices.
Problem: grep_files could effectively hang on certain patterns/files in the sync regex scanning path.
Root causes (two defect classes, fixed across two sessions):
|, including escaped occurrences (\|) and inside groups with escaped parentheses โ malformed/partial regexes. Fixed with an escape-aware splitter. (Optional jest regression test for the splitter: still open, see "Open items" in session notes.)New hard limits (grep_files):
GREP_SCAN_DEADLINE_MS = 15000 โ total scan deadline; results returned as partial + aborted: true.MAX_LINE_CHARS_REGEX_MODE = 20000 โ lines longer than this are skipped in regex mode.PER_REGEX_TIMEOUT_MS = 500 โ per-regex budget; exceeded โ abandon that candidate and continue.Promise.race at deadline + 5 s.Verification: build produced after 23.08.19:22, reinstalled by user, verified byte-exact in installed dist/index.js + index.mjs.
Change: [AutoTracker] [DELTA] log lines now include a live chat-used token estimate, e.g.:
โฆ | chat used โ N tok โฆ
src/tokenStatsManager.ts (recordToolResult): turnBaselineTokens (TokenCheck baseline captured at turn start) + midLoopEstTokens.+delta โ turn total โ chat used.Math.round) at the log site only; rebuilt as build#3, bundle-verified by exact +12 B size delta + content grep, reinstalled by user.(One-line pointers only โ full details remain in the archived CHANGELOG.md.)
.bak files project-wide + fresh full backup; both fixes live in installed plugin (checkpoint 22.08, 20:30).future_improvements/autoTracker_midloop_fix_plan.md) after the earlier predictionLoopHandler approach failed on LM Studio core exclusivity (tools provider XOR prediction loop handler).plan_...wybqqwk1v).โ ๏ธ Superseded by
CHANGELOG_v3.mdon 20.09.2026 (file-size rotation, same house pattern as the v1 โ v2 hand-off). This file is preserved as history โ no new entries are added here; the newest 8 entries below were moved verbatim intoCHANGELOG_v3.mdat rotation time. Older entries ([12.09 READS-EXTENSION]and earlier, plus the mid-file release-history paragraph) remain in this file unchanged.
| Entry | Date |
|---|---|
| SPEC-C (memory-store data-loss arc; dual-writer interleave root fix) | 20.09 |
| F4: CWD state file relocated to persistent data dir | 19.09 ~14:3x |
| Cluster-aware tool ordering wired into toolsProvider | 18.09 ~15:5x |
| AutoTracker F1+F2: mid-loop checkpoint hysteresis + pre-compression notice | 17.09 ~18:4x |
| DE-STRAngle: pattern_scan wall-clock cap removed; deterministic size bounds | 16.-17.09 ~19:3x |
| DRAIN-GRACE: worker-pool thrash fix (delayed idle-drain, 500 ms) | 15.09 ~19:0x |
| SEARCH-NORM: search case- AND word-separator-insensitive | 15.09 ~17:4x |
| RIPGREP TOOL SWAP: grep_files removed โ standalone ripgrep tool | 14.09 ~18:0x |
manage_projects absorbs registry reads (info / list / search)Context: same-evening follow-up to the MANAGE remodel โ user asked which further tools could be merged into a unified action tool; candidate analysis by entity-lifecycle + parameter-overlap criteria picked extending manage_projects with read-only actions as the top fit (same entity ProjectRegistryData, same file, zero new state).
Changes (src/tools/contextManagementTools.ts):
Tests: new manager-level suite block in tests/manageProjects.test.ts ("READS-EXTENSION", 9 tests): getProjectByPath known/unknown (pins the info not-found branch), getAllProjects freshโ[] + round-trip, search name-substring (case-insensitive) / path-only branch / maxResults cap / no-match โ [] with has_projects status vs fresh registry missing status (F1' gate inputs pinned). F1' status semantics themselves remain pinned in projectRegistryStatusMigration.test.ts โ deliberately not re-asserted. No existing test pins the tool-level response shapes of the absorbed tools (scan-verified) โ alias shape-preservation is by construction; expect the prior green baseline + 9 new cases, zero churn elsewhere.
Verification: โ
CLOSED 12.09 ~23:29 โ user confirmed "all tests PASS" (typecheck + jest green; one syntax error in the NEW test block itself was caught on first run and fixed pre-confirmation: F1' apostrophe inside single-quoted test titles โ reworded to F1-prime; zero product-code impact). No version bump โ folds into the pending v1.9.18 proposal (rev 30) alongside MANAGE + FIX #32; release decision remains with user. Adds to the release .bak sweep: src/tools/contextManagementTools.ts.bak, src/locales/{en,de,es,zh-CN,zh-TW}.ts.bak, tests/manageProjects.test.ts.bak.
updateSessionIndex silent skip for orphaned per-copy legacy index (recurring log-noise class)Context: user spotted a recurring [ERROR] line in LM Studio's logs at 12.09 ~22:22 ([StateManager.updateSessionIndex] Project 'ai_toolbox' is not yet registered in indexโฆ). Forensics (all live-verified same session): updateSessionIndex() โ invoked after EVERY state save โ requires an entry for the current project in <PLUGIN_ROOT>/.session_index.json, a PER-COPY file next to the running bundle. The install dir holds no such file, and since REG-MOVE no code path seeds one anymore: registration lives exclusively in the persistent data-dir registry (~\.lmstudio\extensions\data\crunch3r\ai-toolbox\project_registry.json). Consequence: on every fresh copy (i.e., after each lms dev --install wipe) the warn fired on every save_memory/save_session_summary, and its suggested remedy was unperformable โ manage_projects(action='register') never writes to that file. Zero data impact: memory + summaries persist via working-dir msgpack stores, sessions.json index unaffected; only the legacy last_session_saved bookkeeping in the orphaned file was skipped (its sole consumer is ProjectRegistryManager's READ-ONLY migration fallback _loadFromSessionIndex). Side finding: the 22:22 line predates a 22:23:23 reinstall (install-state.json at=1789244603563), so it came from a stale in-memory bundle (old wording); on-disk already carried today's code.
Change (src/stateManager.ts, else-branch of updateSessionIndex(), ~10 lines): warn โ silent return with a FIX #32 comment documenting the orphaned-by-design state and pointing at Option C (full mechanism removal) as tracked follow-up. Registered-entry behavior unchanged (timestamp update + registeredโactive promotion).
Tests: no test pinned the old warn (scan-verified across tests/); existing session-index suites cover the registry's read-only legacy fallback, which is untouched โ expected to stay 779/49 green with zero test changes.
Verification: โณ user-run: npm run typecheck + targeted npx jest tests/stateManager.test.ts tests/manageProjects.test.ts --silent (or full npm test, expect 779/779 ยท 49/49). Live proof after sync+restart: any save_memory in a live chat โ NO [StateManager.updateSessionIndex] line in main.log.
Versioning: no bump โ folds into the pending v1.9.18 proposal (rev 30) alongside MANAGE; release decision remains with user.
register_project โ manage_projects, with unregister/update/clear_all + tombstone protectionContext: user request "register project" surfaced that the registry had no way to REMOVE a project once registered, and stale cross-stamped session-memory entries could silently resurrect unregistered paths via _syncFromSessionMemory(). Design decision (user-approved Option B): remodel register_project into one mutation tool manage_projects with four actions; register_project stays as a deprecated alias for one release cycle.
Changes:
Tests: new suite tests/manageProjects.test.ts (7 tests: unregister+tombstone, no-op for unknown path, re-registration clears tombstone, update rename/sourceDirs without creation, update no-creation contract, clearAll preserves tombstones, resurrection-block test with a control proving the sync path is exercised). One pre-existing assertion updated (projectRegistryStatusMigration.test.ts F1' note now routes to manage_projects).
Verification: โ
Full npm test green 12.09 ~22:15 โ 779/779 tests, 49/49 suites. During this run one fixture defect surfaced in the new suite itself (implementation was correct): the resurrection-block cross-stamp memory file had been written as raw JSON bytes into a .ai_toolbox_memory.msgpack that _syncFromSessionMemory() decodes via @msgpack/msgpack โ first byte 0x5B decoded as fixint โ non-array โ source skipped silently, so the "ghost NOT resurrected" assertion passed VACUOUSLY while the no-tombstone control (which must pass to prove the sync path is live) failed. Fix: fixture now written with encode() from the same library (tests/manageProjects.test.ts); post-fix the tombstone logic is genuinely exercised on both sides. โ
User-run gates all green by ~22:17: npm test 779/779 ยท 49/49 (22:15) + npm run typecheck clean (analyzer tsc integration inert here โ filesChecked 0, hence user-run). All verification gates closed. No version bump yet โ release decision pending (proposal on table: v1.9.18 / rev 30 per house convention).
Context: 11.09 reinstall forensics proved lms dev --install wipes the plugin dir โ including BOTH registry sources (project_registry.json + .session_index.json) โ while .ai_toolbox_state.json can still hold a VALID workingDir. Two live defects followed: (a) post-reinstall, list_projects()/search_projects() returned [] for the whole session although durable session-memory stamps existed; (b) test runs leaked fake tmp working dirs into <repoRoot>/.ai_toolbox_state.json, which lms dev --install shipped into the plugin snapshot and broke boot CWD resolution live (canary d).
Changes:
Verification: user-run full npm test 772 passed / 48 suites at 20:21 and again 21:01 after FIX #31b. Anti-leak verified without re-running (delegated-proof): dev .ai_toolbox_state.json content = valid dev path + mtime 18:42Z (pre-test-run) โ untouched during the green run. Deployed live ~20:35; canary a โ (data-dir registry primary+.bak written by the live process, content verified), b unreachable by design this cycle, c code-reviewed only.
Versioning: no bump โ v1.9.17 rev 29 stays current; package.json description updated to "772 passing tests across 48 suites". Canary d โ closed 12.09 ~21:2x: plain LM Studio restart โ fresh chat WITHOUT cd โ tool-process CWD already resolved to the dev tree before any explicit pin (previous_directory = dev path at session start), relative ops landed in the dev tree. All four canaries now closed (a โ live-verified, b unreachable by design this cycle, c code-reviewed, d โ).
Context: LM Studio's getPluginConfig() is PER-CHAT scoped and falls back to schematic defaults for values the host has not explicitly persisted, so a toggle flipped in one chat could reset in the next โ user tool choices had no plugin-side memory. Agreed design (08.09): auto-capture + sparse storage + conservative "sticky keys" contract; booleans only (never paths/tokens/secrets).
Changes:
Verification: all four gates green (user-run 08.09): typecheck 0 errors ยท ESLint 0 warnings ยท targeted jest both suites green ยท full npm test 761/761 PASS. Live proof: installed source-run plugin captured real user toggles into %USERPROFILE%\.ai_toolbox\tool_gating_profile.json (browserAutomation, executionShell, executionTests = true, written 08.09 ~21:28 local).
Versioning: released as v1.9.17 โ package.json v1.9.16 โ v1.9.17 (description "761 passing tests across 46 suites"); manifest.json revision 28 โ 29 so LM Studio detects the update; installed-copy metadata mirrored same session (live install runs from source, verified byte-identical to dev pre-bump). Pending: re-compress tests.7z before Hub publish (lms push) โ tests/ changed since last archive.
web_search zero-result fallback fix (dead engine no longer stops the chain)Context: live network probe (08.09, datacenter IP) showed ddg-api blocked by DDG anomaly detection and Google returning an HTTP-200 JS-shell/consent page with zero parseable results. The old searchWithFallbackChain treated a 0-result engine response as success (success:true, count:0) and STOPPED the chain there โ Bing (verified working from this IP, 11 results) was never reached.
Change (src/tools/webResearchTools.ts, minimal):
Post-install verification (same day, this session): rebuild + reinstall ~17:28โ17:30 (install-state at=today 17:29:50; shipped bundle .lmstudio/production.js contains all fix markers ร1). Live test web_search("Node.js latest LTS version") โ success, count=10, engine=ddg-fetch โ log line L3268 @17:36:28 shows Search engine "ddg-api" failed: DDG detected an anomalyโฆ followed by correct chain advance (today the dead first engine took the hard-fail path; the zero-result soft-fail line is covered by the regression tests + bundle markers). Zero worker-pool rollback-trigger lines post-restart.
Versioning: released as v1.9.16 โ package.json bumped v1.9.15 โ v1.9.16 (description: "748 passing tests across 45 suites") and manifest.json revision advanced 27 โ 28 so LM Studio detects the update; reinstall + restart completed same day, fix verified live above.
ripgrep promoted to runtime dependency for Hub installsContext: post-rev-26 live verification on the user's machine (03.09) confirmed B' phase-1 LIVE locally โ then exposed a packaging gap for everyone else. The ripgrep fast path (pattern_scan B' + grep_files rg engine) resolves the npm package ripgrep via lazy dynamic import at tool-call time, but it was declared in devDependencies. Official LM Studio docs (verified 03.09): a Hub install auto-downloads dependencies from package.json+package-lock.json with bundled Node v22.21.1 and never runs postinstall scripts โ so any production-scoped (--omit=dev) install would omit the dev-flagged package โ first engine call resolves {status:'fallback-required', reason:'missing-dependency'} โ every Hub user silently pinned to pure-JS fallback forever (stdout invisible in main.log, no error surface). Local dev installs mask this because a full npm install populates devDeps too.
Change (commit ec783a8): "ripgrep": "^0.3.1" moved devDependencies โ dependencies; lock entry loses its "dev": true flag โ pin stays exactly 0.3.1 (the build live-verified this session; no version chase mid-release). Same commit's npm normalization also synced the stale lock root version (v1.9.12 โ v1.9.15, stale since C3) and applied within-range transitive patch bumps only (browserslist chain [dev] + @xmldom/xmldom 0.8.13โ0.8.15).
Verification: lock diff audited = exactly the planned flips; smoke suites after refresh: ripgrepEngine.test.ts + patternScanBPrime.test.ts 39/39 PASS incl. real-WASM integration (resolution unaffected). End-user proof only exists on a clean Hub install โ one-line check when testable: does node_modules/ripgrep/ exist after first download?
Versioning: no version-number change โ v1.9.15 stays current; manifest.json revision advanced 26 โ 27 so LM Studio detects the update (pure rev-bump precedent: v1.9.10 rev 20โ21, 28.08). Re-publish via lms push.
pattern_scan ripgrep phase-1 candidate prefilter (B')Context: plan_1788368034162_2jkd1nvxv. B' ports the grep_files v1.9.13 Option A architecture verbatim to pattern_scan: regex-mode directory scans first run an in-process WASM ripgrep prefilter that names the files whose content can match; ALL line shaping and every resource gate stay in the existing JS worker pipeline, so any non-ok engine outcome leaves output byte-identical to the pre-B' behavior. Goal: cut per-line .test() CPU cost (and ReDoS exposure) on regex-mode scans without changing any visible output field, cap, or skip-record contract.
Changes:
Session triage (02.09, two new-feature defects fixed during verification):
let effectiveMaxDepth = SCAN_DEFAULTS.maxDepth; carried a narrowed literal type (10) from the frozen defaults object โ TS2322 at the dir-branch reassignment. Fixed with an explicit number annotation (type-level only, zero runtime effect).pre-B' inside a single-quoted string) terminated the literal early โ 7-cascade TS1005/TS1128. Fixed by escaping to , matching the file's own convention.Verification (user-run, 02.09): typecheck / lint / build all PASS; full Jest suite ALL PASS GREEN. Both engine regimes were empirically exercised on the dev machine: a targeted two-file run downgraded under jest (ESM import) โ byte-exact fallback path proven (46/46); the subsequent FULL-suite run proved phase-1 LIVE in the same environment โ the pre-analyzed binary-test failure appeared exactly once with precisely the documented divergence, confirming live behavior before the tolerant rewrite. Host-runtime performance follows the grep_files precedent (3.4x/2.8x on hosts b/c/d/e); per pinned observability facts, post-install engine state is verified via tool-response fields (stats.filesScanned differential), since plugin stdout never reaches main.log.
Versioning: released as v1.9.15 โ package.json + manifest.json bumped v1.9.14 โ v1.9.15 on 02.09, revision advanced 25 โ 26 so LM Studio detects the update (user-directed). Suggested commit: pattern_scan B': ripgrep phase-1 candidate prefilter (Option A parity, fallback-guaranteed).
get_memory local-file parse guard (hotfix)Context: post-v1.9.13 reinstall verification surfaced a pre-existing defect in get_memory's PRIORITY-1 read path (src/tools/contextManagementTools.ts). Every call logged to plugin stderr: [ContextManagement.get_memory] Local file parse failed: TypeError: Cannot read properties of undefined (reading 'startsWith'). Falling back. โ first observed 02.09 ~17:43, reproducing on the fresh v1.9.13 process at ~18:07 after reinstall.
Root cause: .session_context/.ai_toolbox_memory.msgpack in a working dir is shared storage for two record families: save_memory facts ({key,value,timestamp}) and auto-context entries from the context-management/auto-summarize stream ({id,title,type,content,tags,scope,frequency,project_path,timestamp[,date]}). This project's file held 154 records โ only 3 with a string key; the other 151 have none. The reader filtered with e.key.startsWith('memory_') โ TypeError on the first keyless record โ the whole try-block aborted and PRIORITY-1 (local project file, the documented #1 source) silently died; every read fell through to plugin-root/RAM fallbacks. Writes were never affected (save_memory kept persisting); only reads degraded โ facts looked present in RAM but would not survive a process restart from disk.
Fix (2 lines, contextManagementTools.ts only): the key filter is now null-safe at both read sites โ local project file and plugin-root fallback:
No schema change, no migration, no new storage separation; context-shaped records are now skipped by the filter (their purpose) instead of throwing. The third startsWith('memory_') occurrence in the same function (over string keys from Object.keys) was already safe and is untouched.
Verification: static re-read of both patched lines + CRLF integrity audit (file remains 100% CRLF); user-run gate pending: npm run typecheck, npm run test:quiet, npm run build, then reinstall v1.9.14 โ get_memory expected to return all 3 local memory_* facts with no parse-failure line in main.log.
Versioning: released as v1.9.14 โ package.json + manifest.json bumped v1.9.13 โ v1.9.14 on 02.09, revision advanced 24 โ 25 so LM Studio detects the update (user-directed).
grep_files ripgrep-backed regex engine with JS fallback (Option A)Context: plan_1788282568340_z5a4r521c, gated end-to-end by P0 spike data (scripts/rg_spike.mjs, T1โT4 โ kept as the diagnostic/evidence generator) and a pre-change characterization battery. Goal: cut per-line CPU cost of regex-mode scans (and ReDoS exposure on the inline path) without changing any visible output field, hang guard, or skip-record contract.
Changes:
Verification (user-run, G9): four verification rounds โ round-1 triage โ fixes on disk โ round 2: two root causes found and minimally fixed:
Versioning: released as v1.9.13 โ package.json + manifest.json bumped v1.9.12 โ v1.9.13 on 02.09, revision advanced 23 โ 24 so LM Studio detects the update (user-directed). Suggested commit: feat(grep_files): ripgrep-backed regex engine with JS fallback.
Context: full dead-code audit of all 140 TS files in src/ + tests/ (AST unused-export detection cross-validated by import-graph reconstruction). Tier-1 = confirmed orphans with zero referencers anywhere. Removal executed stepwise per user constraint (โค5 destructive ops/phase, checkpoint after each phase; plan_1788278947185).
Deleted โ 13 files (~90.3 KB / ~1739 LOC):
Behavior-neutral by construction: tsup entry is src/index.ts only โ none of these modules were ever in the shipped bundle. Zero config edits (jest.config.cjs re-read in full: live ./tools/utilityTools.js mock mapper intact; no entry referenced a deleted path).
Evidence-gate lesson (no blind deletion): audit initially flagged tests/grep_files.test.ts as importing nonexistent ../src/utils/helper. Full-file read proved the string exists only inside test-fixture template literals (fs.writeFile fixture content) โ an import-graph false positive from matching file text, not real imports. Gate held: green baseline + clean imports โ test kept.
Verification (user-executed): BEFORE โ typecheck 0 errors / Jest all-green / build success; AFTER (all 13 deletions) โ identical results (npm run typecheck, npm run test:quiet, npm run build). No suite added/removed/broken.
Docs sync (same session): current-state docs aligned with code โ ARCHITECTURE.md module tree + recode-rule sections, TOOLS_REFERENCE.md rule table; historical entries in CHANGELOG/RELEASE_NOTES left intact as history.
Versioning: no bump โ v1.9.12 rev 23 stays current. Folds into the pending user deploy (together with the executedTool transparency stamp from the entry below). Suggested commit: chore: remove audited Tier-1 dead code (~90 KB).
executedTool transparency stamp in the toolsProvider wrapper (silent-substitution incident follow-up, option B)Context: "silent tool substitution" incident (user's own LM Studio logs 2026-09-01, 16:18โ16:23): with grep_files disabled, a chart ran instead; after disabling generate_chart, run_python ran โ plus false success narration and stray PNGs. Attribution closed from log evidence: model/generation-side tool substitution + transcript observability gap โ NOT an ai_toolbox routing defect (host remapping unproven; a 16:20 tool-card screenshot would be the only remaining way to fully exclude it). The wrapper already logged the true executed name ([AutoTracker] [DELTA] โฆ from <name>), but that ground truth never reached the chat transcript.
Change (src/toolsProvider.ts, instrumentedImplementation): after the measurement/guard try-catch, plain-object results gain one additive field โ executedTool = the registered (minified) name of the implementation that actually executed: { ...result, executedTool }. Strictly additive:
Object.prototype) gain exactly ONE key. No tool emits this key today (grep-verified across src/ before introduction); if a collision ever appeared, the wrapper value is authoritative (spread order).Tests: new suite tests/executedToolTransparency.test.ts (8 tests) runs the REAL pipeline (registration โ minifyTools โ instrumentation wrapper) with six side-effect-free probe tools injected through one registry module โ mocked at the moduleNameMapper'd stub path (../__mocks__/markdownPreviewTools.js) so it intercepts the provider's actual dependency graph. Coverage: stamp === registered name; per-tool identity across probes in a single run; additive-only key contract (original fields verbatim, exactly one key added); byte-identical passthrough for string/number/array/null; FIX #20 A1 regression guard (recordToolResult once per success, zero on failure โ pre-existing semantics preserved); error propagation unchanged; godMode sweep (unique non-empty names + wrapper contract across every exposed tool).
Verification status:
{} / array / string / number / boolean / null / undefined / class instance / Buffer / Date / source non-mutation / collision precedence / unknown_tool fallback).npx jest tests/executedToolTransparency.test.ts, then full baseline npm test โ expect 657 existing + 8 new green. Sandbox cannot execute (child_process blocked by policy).Deployment: the live install runs from source (src/) โ sync src/toolsProvider.ts into the LM Studio install folder and fully restart LM Studio. No rebuild/reinstall required (bundles carry no version strings).
Versioning: no bump โ v1.9.12 rev 23 stays current. Candidate for a future release decision (v1.9.13 or fold-in); manifest untouched in this session.
pattern_scan tool + puppeteer connected property-read fix + dead-file removalChanges:
Versioning: released as v1.9.12 โ package.json + manifest.json bumped v1.9.11 โ v1.9.12 on 31.08 (user-directed); manifest revision advanced 22 โ 23 so LM Studio detects the update.
grep_files completion telemetry (live-verified same day; closed counter audit + exonerated plugin in LM Studio freeze incident)Change (src/tools/fileSystemTools.ts): two per-call wall-clock lines added to the grep scan, both via console.warn โ stderr โ the ONLY channel LM Studio persists to %APPDATA%\LM Studio\logs\main.log (stdout is dropped), accepted cost: one [error]-tagged line per grep call.
[grep_files] completed in <N>ms โ <filesScanned> file(s) scanned, <matches> match(es), <skipped> skipped (appends [ABORTED โ partial results] when the internal controller is set).[grep_files] aborted in <N>ms (host/timeout) โฆ [partial results].Why: log forensics should be able to separate scan time from model-generation time per call โ the "60 s+ grep waits" class of reports. Telemetry proved the engine fast (2โ17 ms observed in production on 30.08) and localized perceived wait to LLM generation around sub-tenth-of-a-second tool calls + LM Studio host-side cancel behavior.
Verification (same day, post-rebuild/reload): five consecutive production lines matched their JSON results exactly โ 4ms/8 files/20 matches ยท 2ms/0 scanned-1 skipped (correct: size-skipped file never reaches the search gate) ยท 3ms/1/14 ยท 17ms/77/0/2 skipped ยท 5ms/1/16. Full counter audit closed: filesScanned++ only after the size gate (~L2391) and line-cap gate (~L2452); skip-push sites L2376 (size) / L2444 (line cap) / L2469 (worker kill).
Incident context (same evening): an LM Studio 0.4.23 app freeze at ~17:49 (prediction-loop stall on tool dispatch; Canceling predictions timed out unhandled rejection + engine SIGTERM; recurring 08-26โ08-30) was root-caused as host-side โ the orphaned call left zero trace in the plugin, and every call that reached the plugin logged correctly. Upstream issue draft: LM_STUDIO_ISSUE_2026-08-30_prediction_loop_stall.md.
Problem (residual hang class after FIX-HANG-1..4): a single catastrophic-backtracking RegExp.test() on the main thread blocks the event loop and starves EVERY timer โ including the 15 s scan deadline, the 30 s fallback AND the 20 s wall-clock backstop. No cooperative gate can preempt synchronous JS; only another thread can.
Fixes (src/tools/fileSystemTools.ts):
Evidence: probe artifacts + results archived to docs/history/GATE_PROBE_EVIDENCE_fixhang5c.md and docs/history/FIXHANG5_REDOS_RESULTS.md (code comments reference the new locations; both files removed from disk 08.09 โ recover from git history, e.g. git show d319c92~1:docs/history/FIXHANG5_REDOS_RESULTS.md).
grep_files limits (docs-only, no code change)grep_files (code-verified + LIVE VERIFIED same day)Problem: prose alternations containing a bare & โ e.g. "Backup & Restore|Git & GitHub" over TOOLS_REFERENCE.md, which contains both phrases โ were silently forced into literal mode โ 0 matches, triggering LLM retry loops. Root cause pinned in isSafeRegex() (src/security.ts): the code-signature heuristic pair /([*+?&]/ + /[\w][*&]|[\*&]\s+\w/ fired on "& Restore" / "& GitHub" even though & is not a JS regex metacharacter (zero backtracking risk) โ i.e. a pure false positive.
Fixes:
Tests: 3 new regression specs in tests/security.test.ts covering the REV-24 decision boundary; full suite re-run green at 628/628.
Verification status:
tsc --noEmit OK ยท tsup build OK ยท typecheck OK ยท lint OK ยท jest .Deployment note: the live install runs from source (src/ via entry.ts, no dist/ in that folder) โ a direct src-file sync + full restart is sufficient; no rebuild/reinstall required. Lesson logged: patternMode:"literal" on a known-containing fixture โ suspect stale runtime first, compare installed-copy mtimes before re-opening the code.
Versioning (updated 28.08 ~20:45): shipped as part of v1.9.11 โ package.json + manifest.json bumped v1.9.10 โ v1.9.11 per user decision, superseding the 25.08 "no bump" policy; manifest revision field stands at 22. (No dist rebuild required for this hotfix itself โ installed copy runs from src/; baseline backup ai_toolbox-v1.9.11-release-baseline-2026-08-28.zip taken pre-cleanup.)
Problem: persistent-memory round-trips silently lost โ get_session_summary() / get_memory() returned empty despite all 50 sessions existing in .session_context/sessions.json. Root cause: StateManager fresh-start overwrote .ai_toolbox_memory.msgpack before/during initialization, plus two related races in the cache-rebuild and origin-tracking paths.
Fixes (src/stateManager.ts):
saveToFile() can run before readiness โ added ensureReady gating at the save site (~L345) so writes never race initialization.Tests: new deterministic regression suite tests/stateManagerRace.test.ts (5 tests) reproducing each race without timing luck; full Jest suite green pre-deploy, lint clean.
Build & deploy: rebuilt dist/ via tsup (worker-thread sandbox workaround for the no-shell session environment); bundle-verified B1/B2/B3 markers present in fresh build (ensureReady at save site, _rebuildKeysCache merge, conditional _origin). manifest.json revision 20 โ 21 (pre-bump state preserved as manifest.json.bak). User re-deployed ~16:34 same day.
Live verification: context write/read round-trip proven in the new process โ milestone entry persisted across restart, store diagnostic reports 1 record; no further memory-loss events reported.
Versioning: no version bump โ v1.9.10 stays current (maintainer policy); only manifest.json revision advanced to 21 so LM Studio reloads the plugin.
Problem: deterministic multi-day V8 heap OOM (Ineffective mark-compacts near heap limit) in the vector-RAG chunkers. Root cause proven by plain-Node repro: chunkText / chunkDocxText / chunkPdfText (src/tools/vectorRagTools.ts, ~L212/277/331) advanced with a fixed stride while the partial final chunk was shorter than the overlap budget โ for certain word-count remainders the window start reached a fixed point (startIndex === endIndex) and the loop never terminated (repro: len=34,209 words, size=500/overlap=50 โ stalls at start=lenโ50). Most real documents terminate "by luck"; poison remainders do not.
Fixes:
Verification:
tests/webResearchTools.test.ts and the new oversized-page regression spec.npm run build; bundles carry no version strings, so a normal rebuild suffices).Versioning: no version bump โ v1.9.10 stays current. This supersedes the "candidate for v1.9.11" notes in the three 24.08 entries below (maintainer decision 25.08: version number stays at 1.9.10).
Context: screenshot failure (Tool call failed โฆ Errors: WebSocket closed by the client on rag_web_content() + fetch_web_content(), Weinstein Wikipedia URL). Forensic verdict: that string exists NOWHERE in src/ (grep-verified) โ it is LM Studio's transport-level report for an in-flight tool when the plugin host process dies (same-day exit-134 heap-fatals). Both tools failed within ~5 s โ common cause = host, not two independent bugs. rag_web_content was still the worst transient allocator on web paths and guaranteed-to-fail on real Wikipedia articles (page > hard cap โ max allocation consumed, then success:false).
Changes:
Tests:
tests/vectorRagTools.ragWebContent.test.ts (4 tests + sanity): markup-stripping contract (bestMatch.text contains prose, no ), soft-cap contract (oversized stream โ , , stripped chunks), error contract preserved (), real- guard.Verification status:
filesChecked: 0) โ run locally: npm run typecheck, then npx jest tests/vectorRagTools.ragWebContent.test.ts tests/webResearchTools.test.ts --silent (or full npm test). Expect green; the two rag suites are the only behavior changes.success:true with readable chunks (page now truncates softly instead of failing), no "WebSocket closed by the client". If a host OOM recurs, the line names the next suspect.Known residuals (tracked, NOT changed): LocalVectorStore in vectorRagTools.ts grows unbounded with repeated rag_index_* calls (no size cap like the other LRU caches got) โ candidate for OOM part 3 if indexing-heavy sessions recur. GitHub-API .json() reads (gitGithubTools.ts) remain unbounded (small internal payloads, unchanged since part 2).
Versioning: no version bump โ folds into v1.9.11 together with the OOM parts 1+2; bump package.json + manifest.json at release time. Bundle check after rebuild: name:"rag_web_content" must remain exactly 1ร per dist file (dedup invariant since v1.9.10).
Problem: two more Ineffective mark-compacts near heap limit host kills today (~20:24 and ~21:10), both ~40 s after a fresh plugin start while web-search tools were in flight. The part-1 guard was live and working (both windows show its Page too large (> 48.8 KB streamed) size-cap error firing correctly) โ but it only covered fetch_web_content + rag_web_content.
Evidence from server log (2026-08-24.1.log) for the ~20:24 crash: Search engine "ddg-api" failed: DDG detected an anomalyโฆ โ fallback chain took over (unbounded .text() paths) โ death. The same correlation holds for ~21:10 (web_search in flight, no DELTA completion logged).
Fixes (all minimal, error contracts preserved):
Known residuals (tracked, NOT changed โ out of scope for this fix): gitGithubTools.ts GitHub-API .json() reads, lmStudioApi.ts local-API reads (small internal payloads), dead file tools/networkToolsRegistry.ts (still zero imports โ deletion remains backlog). The exact GB-scale allocator is not yet proven from static analysis alone; the watchdog exists precisely to identify it in one more crash if they recur.
Verification status:
npm run typecheck, npx jest tests/webResearchTools.test.ts --silent, then rebuild + reinstall, then live web-search test (user testing immediately).[HEAP-GUARD] line + the last [DELTA] lines in the server log identify the responsible tool without a debugger.Versioning: no version bump โ candidate for v1.9.11 alongside the part-1 OOM guard; bump package.json + manifest.json revision at release time.
Fixes a class of hard plugin-host crashes: FATAL ERROR: Ineffective mark-compacts near heap limit when fetching large page bodies.
Root causes (proven by code read, session 24.08.2026):
fetch_web_content: await response.text() materialized the ENTIRE body before its 50 KB check ran โ oversized pages allocated their full size regardless of the cap.rag_web_content (): raw unbounded , no cap at all, plus ~5โ10ร memory amplification in word-array chunking/embedding, and no timeout.Fixes:
Verification status:
Page too large (> 48.8 KB streamed) โ the bounded reader is active in the installed build and the host stayed alive (pre-fix wording would have been (177.2 KB) from full buffering).npm run typecheck + npx jest tests/webResearchTools.test.ts --silent to be run locally (session environment has no shell).Versioning: no version bump in this change โ candidate for v1.9.11; bump package.json + manifest.json revision at release time.
Out of scope (tracked): the three search-engine fallback functions still use raw .text() on known-small result pages; dead file tools/networkToolsRegistry.ts removal.
Fixes:
Versioning: package.json v1.9.10 + manifest.json revision 20. Bundles do not embed version strings (proven in the v1.9.9 release), so a normal rebuild suffices.
Problem: grep_files could effectively hang on certain patterns/files in the sync regex scanning path.
Root causes (two defect classes, fixed across two sessions):
|, including escaped occurrences (\|) and inside groups with escaped parentheses โ malformed/partial regexes. Fixed with an escape-aware splitter. (Optional jest regression test for the splitter: still open, see "Open items" in session notes.)New hard limits (grep_files):
GREP_SCAN_DEADLINE_MS = 15000 โ total scan deadline; results returned as partial + aborted: true.MAX_LINE_CHARS_REGEX_MODE = 20000 โ lines longer than this are skipped in regex mode.PER_REGEX_TIMEOUT_MS = 500 โ per-regex budget; exceeded โ abandon that candidate and continue.Promise.race at deadline + 5 s.Verification: build produced after 23.08.19:22, reinstalled by user, verified byte-exact in installed dist/index.js + index.mjs.
Change: [AutoTracker] [DELTA] log lines now include a live chat-used token estimate, e.g.:
โฆ | chat used โ N tok โฆ
src/tokenStatsManager.ts (recordToolResult): turnBaselineTokens (TokenCheck baseline captured at turn start) + midLoopEstTokens.+delta โ turn total โ chat used.Math.round) at the log site only; rebuilt as build#3, bundle-verified by exact +12 B size delta + content grep, reinstalled by user.(One-line pointers only โ full details remain in the archived CHANGELOG.md.)
.bak files project-wide + fresh full backup; both fixes live in installed plugin (checkpoint 22.08, 20:30).future_improvements/autoTracker_midloop_fix_plan.md) after the earlier predictionLoopHandler approach failed on LM Studio core exclusivity (tools provider XOR prediction loop handler).plan_...wybqqwk1v).manage_projects absorbs registry reads (info / `listupdateSessionIndex silent skip for orphaned per-copy legacy inregister_project โ manage_projects, with uweb_search zero-result fallback fix (dead engine no lonripgrep promoted to runtime dependencpattern_scan ripgrep phase-1 candidate prefilter (B')get_memory local-file parse guard (hotfix)grep_files ripgrep-backed regex engine with JS fallback (Option AexecutedTool transparency stamp in the toolsProvider wrapper (silent-supattern_scan tool + puppeteer connected property-read fix + deagrep_files completion telemetry (live-verified same day; closed countergrep_files (code-verified + LIVEmanageProjectsImpl() gains 3 read actions: info (โ getProjectByPath; not-found error text unchanged), list (โ getAllProjects; F1' self-describing empty result carried over verbatim), search (โ search(query, maxResults); default 10 kept; F1' diagnostics attached only when status โ has_projects โ logic moved from tool body into impl without semantic change).manage_projects schema: enum extended to register|unregister|update|clear_all|info|list|search; new optional params query + max_results (1โ50, default 10); working_dir_path now documented as required for register/unregister/update/info; description restructured into MUTATIONS/READS sections โ consent contract preserved. One stale hint updated (unregister's "use list_projects/search_projects" โ action=list/action=search).get_project_infoโinfo, list_projectsโlist, search_projectsโsearch. All delegate to the shared impl and preserve LEGACY response shapes exactly (bare project object / {projects} ยฑ registry diagnostics; no action key), so existing callers/prompts keep working.toolPriority.ts untouched โ none of the four tools has a tier entry there (scan-verified).register_project(...).lmstudio/production.jsmanage_projects tool (src/tools/contextManagementTools.ts) โ flat params + per-action validation (no zod discriminated union, by design): action=register|unregister|update|clear_all; destructive actions require confirm=true. Shared implementation closure; the deprecated register_project alias delegates to it.ProjectRegistryManager.unregisterProject() โ removes an entry via canonical path match (F1 pattern) and records the path in a new bounded unregistered tombstone list (UNREGISTERED_TOMBSTONE_MAX = 50, newest-at-end). _syncFromSessionMemory() now skips tombstoned paths, so stale cross-stamped memory entries can no longer resurrect explicitly removed projects.ProjectRegistryManager.updateProject() โ rename and/or sourceDirs replacement for an EXISTING registration only (never creates; unknown path โ {updated:false}).clearAll() PRESERVES tombstones (a full wipe must not resurrect everything in one sweep). Backward compatible: pre-MANAGE registry files simply lack the field.manage_projects entry + deprecation note on register_project).src/workingDir.ts): restoreLastActiveProjectCwd() now runs bootstrapRegistryFromSessionMemory() BEFORE the idempotent guard, on every boot. The 11.09 bootstrap call sat BELOW the guard and was therefore only reachable when persisted CWD state was missing โ exactly the post-reinstall hazard (valid stale state โ early return โ empty registry for the whole session; observed live 12.09: both tools returned [] although durable stamps existed in plugin-dir + project msgpack stores). Bootstrap is self-conservative: no-op when EITHER registry source yields โฅ1 project, materializes only directories that still exist on disk (no ghosts, no auto-registration โ index.ts rule #2 intact); healthy-case cost = two small JSON reads per boot.project_registry.json (+.bak) relocated from the plugin dir to LM Studio's persistent data dir %USERPROFILE%\.lmstudio\extensions\data\crunch3r\ai-toolbox\ โ survives every lms dev --install wipe. New module src/dataDir.ts (getDataDir(), jest-guarded). Writers: boot-time bootstrap (workingDir.ts) + ProjectRegistryManager (contextManagementTools.ts) now target the data-dir primary with atomic tmp+rename (Windows file-lock fallback); plugin-dir copies kept as read-fallback for the migration window. Readers: listRegisteredProjects() priority = data-dir registry โ plugin-dir registry โ legacy .session_index.json.jest.config.cjs): REG-MOVE's new ../dataDir.js import crashed contextSearch.test.ts ("Cannot find module ../dataDir.js") โ the inserted mapper entry was double-escaped and matched NEITHER spec form. Both dataDir entries (double-dot + single-dot) regenerated byte-for-byte from known-good siblings, verified via require(config)+RegExp.test against every specifier form (hand-typed escapes corrupt across JSON/tool layers).src/workingDir.ts + tests): test-hygiene leak โ workingDir.test.ts's setWorkingDir(tmpdir) persisted fake working dirs into the REAL <repoRoot>/.ai_toolbox_state.json. State-file location is now jest-guarded to a per-run temp dir, resolved LAZY and mock-safe via new exported seam getStateFilePath() (one location per process; try/catch around os.tmpdir with fs.mkdtempSync('') fallback + pid-suffixed last resort that never touches the production file) โ module-scope side effects eliminated. First attempt (FIX #31, module-scope mkdtemp) broke 2 suites: utilityTools.test.ts crashed at setup ("os.tmpdir is not a function" โ its full jest.mock('os') factory lacked tmpdir), cwdConsistency failed against its hardcoded repo-root path. FIX #31b remixed both: utilityTools' 'os' mock gained tmpdir; cwdConsistency asserts against the module-owned path (imports getStateFilePath()). Rule for future on-disk-state assertions: import getStateFilePath() โ never re-derive. Production path unchanged.tests/registryDataDirMove.test.ts + tests/projectRegistryStatusMigration.test.ts; badge updated to 772 tests / 48 suites (badge count cross-confirmed by jest's own "48 total" line).src/tools/toolGatingProfile.ts (~320 LOC): user-level sparse profile at %USERPROFILE%\.ai_toolbox\tool_gating_profile.json โ outside the plugin dir on purpose, so it survives reinstalls/updates; written via shared atomicWriteFile (temp + rename โ crash-proof); missing/corrupt file degrades to empty profile (defaults apply), never breaks registration.syncToolGatingProfile) applied in toolsProvider.ts: โ RECONCILE โ only a live NON-default value that differs from storage is an unambiguous re-toggle and beats sticky storage; default-valued differences (fresh-chat fallback vs explicit revert are byte-indistinguishable per the SDK's per-chat scope) keep the stored value. โก OVERLAY โ all other stored values win over host defaults in new chats. โข FINALIZE โ storage rebuilt from final config, keys at default evicted (sparse), persisted exactly once on change (no steady-state disk I/O); cache ends equal to disk.toolsProvider.ts): fire-and-forget capture + drain before return; .catch(err => {โฆ; return false}).then(() => undefined) keeps the chain Promise<void>-typed WITHOUT a cast (user-caught TS2322 fixed per session rule โ no-cast), wrapped in try/catch so synchronous throws (e.g. path resolution) can never break tool registration. GOD MODE untouched: capture is independent of gating, and godMode=true itself is captured like any other toggle.tests/toolGatingProfile.test.ts (+12 cases; 46 suites total); two round-2 root causes fixed during gate runs: (a) a case assumed webSearch default=false โ schema default is TRUE, so the scenario was flipped to a truly non-default seed ({webSearch:true}, live=false), module behavior proven correct by hand-trace of reconcile/overlay/finalize; (b) cross-test sticky-toggle leakage via the pid-scoped redirect file โ hermetic beforeEach (resetGatingProfileCache() + unlink of redirected profile path).results.length === 0anyEngineEmpty flag distinguishes the final error wording when all engines responded but yielded nothing parseable ("No search results found โ engines responded but returned no parseable results (possibly bot-blocked)") from hard total failure ("All search engines failed"). The <2 sparse-but-real success threshold is unchanged.describe 'web_search zero-result fallback' block in tests/webResearchTools.test.ts โ mid-chain shell pages must not stop the chain (expect engine=bing, count=2, 3 fetch calls) + all-empty โ new wording. Suite 11/11 PASS (9 pre-existing unmodified + 2 new), user-run locally; ESLint 0 problems, tsc clean.src/tools/patternScan.ts (RIPGREP-PHASE-1 block) โ searchCandidates from the shared module (../utils/ripgrepEngine.js, same import identity as fileSystemTools; lazy dynamic import, never breaks boot). Inputs: absolute root, raw trimmed pattern, mode 'literal' iff explicit literal or demoted (else 'regex'), caseInsensitive = !(options.caseSensitive ?? true) โ SENSITIVE default, deliberately NOT mirroring grep_files' hardcoded -i; exclude globs = DEFAULT_EXCLUDE_DIRS only (user globs keep this module's custom semantics in the JS filter); maxDepth for dir roots. Candidate paths are normalized from BOTH rg output formats (absolute and root-relative). On 'ok': named targets go to the workers; every non-named target still passes stat + size gate + a Buffer newline-count read, so 'size'/'line-cap' skip records AND stats.filesScanned stay byte-identical to the full walk (increment at the worker's exact position). ANY non-ok status (no-matches, fallback-required, throw) โ full pre-B' pipeline; no short-circuit even on a clean negative.'binary' skip record for a file rg proved pattern-absent โ binary detection needs content inspection, and such a file is unobservable in every output field except skipped[].tests/patternScanBPrime.test.ts (liveness-gated live battery + unconditional dep-absent reference tests; unique BPRIME_* tmpdir fixtures, TARGET_COUNT=7). Existing tests/patternScan.test.ts binary test rewritten tolerant-style: accepts 'binary' record (fallback regime) OR absent record (live regime), still fails on any third outcome โ a documented divergence, not a silent weakening.patternScan.ts header corrected (the "no dependency on any other tool module" claim is superseded by the ripgrepEngine import) + TOOLS_REFERENCE.md pattern_scan entry gained the B' section with the divergence note.B\'src/utils/ripgrepEngine.ts (~250 LOC) โ self-contained candidate-file filter: lazy dynamic import('ripgrep') (pithings/ripgrep-node 0.3.1, ESM-only WASM build of rg) on first use only โ mirrors the FIX-HANG-5 lazy-load discipline, so a missing/broken dep never breaks plugin boot; exit-code mapping per the package's documented contract (0 โ ok+files ยท 1 โ no-matches clean negative ยท 2 โ fallback-required, incl. Rust-dialect parse errors such as lookarounds/backreferences, detected at compile time in ~2โ3 ms); flag mirroring of production walker semantics (--no-ignore --no-require-git --hidden; -i passed as a parameter because grep_files compiles every regex with 'i'; caller-supplied exclusion globs; --max-depth=cap+1 depth-budget parity quirk, unit-asserted โ cap 0 โ no flag); no --max-filesize (exact-byte size skip records stay phase-2-only). Contract: the function never throws โ every failure mode resolves to a typed fallback signal.src/tools/fileSystemTools.ts (regex-mode + directory target only) โ two-phase split: phase 1 awaits the engine before scan start (its cost sits outside the 15 s GREP_SCAN_DEADLINE_MS window) and builds an allow-set of rg-named candidates; phase 2 runs the EXISTING processWithRegex shaping unchanged on that set (line-split, >20k-char line gate, .trim(), truncation + 'โฆ', 1-based line numbers, include_context, result caps). Single-file targets and AST mode are byte-for-byte untouched paths; any phase-1 non-'ok' outcome leaves the candidate set null โ full-JS walk runs exactly as before, every existing hang guard intact (15 s deadline, per-regex 500 ms abandon-and-continue, worker isolation + 2 s kill for ReDoS-suspect patterns, 30 s fallback timeout, wall-clock backstop).-i always applies in regex mode (existing production semantics, now explicit); rg --hidden mirrors the walker's dot-dir scanning whenever no include pattern is given; the >20k-char line gate and all caps/skip-record contracts are preserved verbatim.max_file_size or over the max_lines cap is reported in skipped_files with byte/line counts identical to the pre-swap records โ pinned by grep_files_hang_backstop, grepFilesParity and the golden baseline.tests/ripgrepEngine.test.ts (incl. real-WASM integration, skipIf-guarded when the dep is absent) + characterization battery tests/grepFilesParity.test.ts against frozen golden baseline tests/fixtures/grepFilesBaseline.json generated BEFORE any src change; AST-mode smoke coverage closed in the same phase.moduleNameMapper rule (^\.\/utils/(.*)\.js$) matched ANY utils require and broke the @babel/* CJS packages (they ship lib/ with .js-suffixed requires) โ replaced by per-file exact mapper entries only.matches: [], filesScanned: 0); fixed by relativizing every candidate against targetDir before allow-set construction (paths outside the tree keep normalized absolute form and can never match โ safe no-op).
Round 3: sole failure 705/706 โ RC-E (round-trip triage): with the rg prefilter 'ok', processFile early-returned non-named files BEFORE both gates, so non-matching over-cap files were silently absent from skipped_files. Fixed in fileSystemTools.ts ONLY: stat + size gate first, then a branch for prefilter-active non-named files โ Buffer read + newline-byte count (lineCount = newlines + 1, exact split('\n') semantics) โ byte-identical line-cap skip record; named-candidate and fallback paths unchanged. Perf trade-off documented honestly: the probe restores pre-swap I/O for gate-passing non-matching files โ the remaining ripgrep win is per-line .test() CPU + ReDoS exposure on the inline path (the spike's ~22ร figure predates parity restoration on an uncontrolled tree, so docs state speed qualitatively).
Round 4: ALL PASS โ typecheck 0 errors / full jest green / build success; parity re-run vs golden baseline = zero unexplained diffs (no one-time baseline special case triggered).src/utils/simulation.ts (self-exec dev script), src/toolsDocumentation.ts, src/tools/imageAnalysisTools.ts (superseded by live imageProcessingTools.ts), src/tools/backupUtils.ts, src/tools/toolProtocolWarnings.tssrc/tools/executionRegistry.ts, src/tools/utilityRegistry.ts (duplicate of live 3-arg registerUtilityTools in utilityTools.ts; no jest mapper targeted them)rules/{deadCodeDetection,asyncModernizer,typeInference,modulePathNormalization}.ts โ engine (recodeEngine.ts), types (recodeTypes.ts) and the LIVE rule rules/unusedImports.ts verified untouched in the same directory listingssrc/toolsProvider.ts.bak, tests/executedToolTransparency.test.ts.baksrc/tools/TokenStatsManager.recordToolResult) still fires exactly once per successful call โ with the ORIGINAL (unstamped) payload and the SAME ground-truth name the stamp now carries.pattern_scan tool (src/tools/fileSystemTools.ts; clean-room engine in new module src/tools/patternScan.ts) โ recursive content search returning matching lines as {file, line, content}. Diff vs grep_files: unsafe/syntactically-invalid regexes fail fast and are auto-demoted to literal mode (reported via demotedToLiteral); fully async with bounded concurrency (concurrency 1โ16, default 4). Hard caps: per-file size gate 256 KB + line-cap gate 10,000 lines (oversize/over-line files reported in skipped[], never scanned), maxMatchesPerFile default 50, global cap 200 (stats.truncated=true when hit); matchLineLength truncation default 300 chars; root accepts a directory or single file, relative roots resolve against the plugin working directory. Jest mock tests/__mocks__/patternScan.ts + mapper entry in jest.config.cjs; full suite green post-wiring (657/657 tests, 38 suites โ user-verified); live probe: visible in running-plugin tool list, stress-probed against the 12.7 MB / 296k-line dist bundle (user-declared SUCCESS).connected property-read fix (src/tools/browserAutomationTools.ts + src/types.d.ts) โ puppeteer 24 exposes Browser.connected at runtime as a getter property, not a method (installed 24.43.1); the local module augmentation now declares it readonly connected: boolean and session-liveness checks read it as a property. Live probe passed same window (browser_open_page โ screenshot save, PNG magic-verified).src/browserAutomationTools.ts (zero imports) deleted behind verified backup ai_toolbox-pre-deadfile-delete-20260831.zip in .ai_toolbox_backups/; typecheck + full jest green post-deletion (user-confirmed). Housekeeping: seven stale .bak files project-wide deleted and re-verified zero.pattern_scan entries added, File System count 22โ23, unique-tool totals 130โ131, Git & GitHub table count corrected to the code's 15; dead-file tree reference removed; screenshot_desktop write lines re-attributed from browserAutomationTools.ts to imageProcessingTools.ts (platform-native subprocess writes the file directly โ no Node-side write; its only atomic-write path is attachment temp-file materialization via resolveAttachmentFile).patternNeedsWorkerIsolation() triage gate: STRICTER than isSafeRegex (which misses brace-bounded nested quantifiers, backreferences, deeply nested groups). Anything the gate cannot PROVE cheap is evaluated for that whole file inside a node:worker_threads Worker (single round-trip; hard-killed via worker.terminate() after WORKER_KILL_MS = 2000 ms). Safe patterns keep the inline fast path โ zero overhead on the common case. Kill/failure โ file recorded in skipped_files, scan continues.$-anchored check let ((a+){3}){4}x defeat triage (T1b double-freeze root cause). Any valid {n[,m]} on an unbounded +/* group body now routes to the worker at any position.self.onmessage) to Node contract (parentPort direct payload) โ the old shape threw self is not defined at boot, so every risky pattern "crashed" and was misreported as a 2000 ms kill (zero regex work). Verified offline in this runtime: safe pattern returns exact indices; T1b-exact catastrophic payload hard-killed at 2000 ms.refactor_code/Recode Engine (only AST-based refactoring in field), AutoTracker + ContextGuard session/token management (mid-loop token deltas firing 75%/90% thresholds inside long tool chains, automatic checkpoint summarization & compression โ absent from every surveyed plugin beyond basic memory CRUD), hang-safe grep_files/find_replace_all, guarded line_operations (MD5 post-write check), background-command suite, browser automation with persistent sessions, integrated multi-format RAG (PDF/DOCX/XLSX + web extraction), run_tests (auto-detects Jest/Mocha/Vitest), planning state machine (create_plan family), secret_scan, data visualization via generate_chart (zero data-viz plugins in field), cross-project memory registry, and the backup/restore suite.grep_files entry corrected to the current contract โ added missing params max_depth (default 10, range 1โ50) and max_lines (default 5000); documented deadline behavior (partial results + aborted: true, per v1.9.9 hard limits) and REV-24 prose-alternation handling (patternMode:"auto_escaped" hints)..bak backups created for both files before editing (pending post-commit cleanup sweep alongside README.md.bak).src/security.ts (isSafeRegex, ~L92โ100): bare & removed from clause 1's char class โ /([*+?]/. Clause 2 (code-signature detection) intentionally kept, so C++-style searches containing & are still auto-escaped โ but only when paired with a genuine unescaped *, + or ?. Inline comment documents the rationale (REV-24).src/tools/fileSystemTools.ts (~L2462/2491): inline heuristic aligned to the same char class; double-quoted hint strings added at the forced-literal sites so a future literal-mode decision is explained to the caller instead of failing silently (mid-session string-quoting compile defect on these lines was caught by user's tsc and fixed, line-level compile-proven).patternMode:"literal", 0 matches) โ forensics proved the active process was executing a stale installed copy (its src/security.ts mtime 21.08 = pre-fix). Both fixed source files synced into the install (C:\Users\root.MPITS\.lmstudio\extensions\plugins\crunch3r\ai-toolbox\src\, marker verified), then: grep_files("Backup & Restore|Git & GitHub", TOOLS_REFERENCE.md) โ patternMode:"regex" + exactly 4 matches (L15/L25 overview rows, L220/L495 headings). Incident closed._rebuildKeysCache() now merges into the existing key set instead of replacing it, preserving entries observed pre-rebuild.saveMemoryFile's _origin handling made conditional so a re-saved file is not falsely flagged as fresh-start data (which triggered the overwrite path).src/tools/vectorRagTools.ts: 3-line termination guarantee in all three chunkers โ startIndex = Math.max(endIndex, startIndex + 1) โ strict forward progress on every iteration. No API/behavior change for well-formed inputs; the stall case now simply emits its final partial chunk and stops.tests/vectorRagTools.ragWebContent.test.ts: regression spec (oversized page) covers the poison-remainder path end-to-end; heading assertion made case-insensitive (html-to-text uppercases <h*> headings by default โ test expectation aligned with library behavior, no source change).tests/webResearchTools.test.ts (test infra only): beforeEach mock isolation fixed โ mockReset() + explicit re-application of base values for the duck-duck-scrape search and performanceUtils.fetchWithRetry mocks. Closed the last failing test, proven to be cross-test order contamination (single-test run with -t passes 1/1; full suite failed without this).src/tools/networkToolsRegistry.ts + its Jest mock tests/__mocks__/networkToolsRegistry.ts deleted. Certainty: zero references in src/, tests/, scripts/; tsup bundles from src/index.ts only (file never shipped); no jest mapper entry for it; both changelog entries of today already tracked its deletion as backlog. The dead file held an unbounded response.text() rag_web_content โ re-wiring risk eliminated.rag_web_content (tools/vectorRagTools.ts):
readCappedText: budget 500_000 โ 250_000 chars; oversized pages now return success:true + truncated:true with usable partial chunks instead of a hard "Page too large" failure after consuming the full allocation (same pattern as the search engines).htmlToText() pass โ previously raw HTML was chunked as "text" (tag soup scored into embeddings; ~40โ60% of budget consumed by markup; bestMatch.text returned unreadable HTML to the LLM).chunks: [{text, score, metadata}]) + truncated flag; bestMatch key preserved (now = topChunks[0]) for backward compatibility. Peak transient allocation per call drops from ~5โ8 MB to ~2โ3 MB.wikipedia_search (tools/webResearchTools.ts): last unbounded read in the web-research path bounded โ await response.json() โ JSON.parse(await readBoundedText(response, 200_000)).<success:truetruncated:trueRAG search failed: โฆhtml-to-texttests/webResearchTools.test.ts: shared fetch mock's default body switched from HTML to a JSON stream (required by change 3; all other assertions unaffected โ htmlToText stays mocked, oversized-page regression tests use their own dedicated mocks).[HEAP-GUARD]src/performanceUtils.ts: new readCappedText(response, maxChars) โ soft cap, stops reading at budget + cancels socket, returns partial content (a truncated search page still yields its top results), never throws on size. Plus checkHeapPressure(toolName) watchdog: pre-call heap probe; logs one [HEAP-GUARD] โ ๏ธ N MB BEFORE "<tool>" started line when usage crosses 1 GB โ if another OOM happens, this names the suspect call in the log immediately before the crash.tools/webResearchTools.ts: all three fallback engines (searchDDGFetch, searchGoogle, searchBing) now use readCappedText(โฆ, MAX_SEARCH_HTML_CHARS = 300_000) instead of unbounded .text(). Worst-case allocation per engine run is now bounded.tools/httpClientTools.ts: all five response-body reads in http_request / http_get_json / http_post_json now go through readBoundedText(โฆ, MAX_HTTP_BODY_CHARS = 500_000) (JSON parsed from the bounded text; oversized bodies fail loudly like fetch_web_content).toolsProvider.ts: wrapper calls checkHeapPressure() before every tool execution.tools/vectorRagTools.tsfetch()fetchWithRetry paths had no per-attempt timeout โ slow/stalled transfers held buffers indefinitely.src/performanceUtils.ts): readBoundedText(response, maxChars) (Content-Length fast-reject with zero reads + streaming chunked read that enforces the budget and cancels the socket early) and WEB_FETCH_TIMEOUT_MS = 30_000; fetchWithRetry now bounds every attempt via AbortController timeout (caller-supplied signals respected).fetch_web_content: 50,000-char cap enforced DURING transfer; error contract preserved (Page too large (โฆ) โฆ Use searxng_search + summary_only).rag_web_content: new 500,000-char budget + shared timeout/backoff helper; error contract preserved (RAG search failed: โฆ).tests/webResearchTools.test.ts (suite oversized page protection): Content-Length fast path asserts zero chunks read + socket cancelled; chunked over-cap stream aborts after exactly 2 of 3 chunks.rag_web_content registration in LM Studio's tool list. Root cause: the vectorRAG registry (tools/vectorRagTools.ts) and the webSearch registry (tools/webResearchTools.ts) both registered a tool with that name, and toolsProvider.ts pushes all registry output without any name-dedup โ duplicate UI entry + non-deterministic dispatch between two different implementations. The keyword "placeholder" implementation (50 KB cap, top-5 sentence filter) was removed from webResearchTools.ts (tool block + now-unused RagWebContentParams interface); the tool is now provided exclusively by the real-RAG version in vectorRagTools.ts.
Verification: user rebuild + reinstall โ UI shows exactly one entry; bundle grep = exactly 1ร name:"rag_web_content" each (dist/index.js L290769, dist/index.mjs L288744); size deltas โ823 B / โ804 B vs. the accepted v1.9.9 build. No tests touched (suite has no assertion on the removed block; count check stays true).
Note: a third definition exists in dead code tools/networkToolsRegistry.ts (orphan file, zero imports โ verified) โ its removal is tracked as backlog, not part of this fix.fileSystemTools.ts, sync-regex path): the per-file line-cap skip message used console.warn โ stderr โ displayed as [ERROR] in LM Studio dev logs despite being an expected, informational event (it is already reported to the caller via skipped_files). Changed to console.log ([INFO]). Genuine anomaly warnings elsewhere were intentionally left untouched.matchGlob anchored the regex before its escape pass โ every include pattern matched 0 files silently. Fixed.max_lines; skipped files reported in skipped_files. New regression suite tests/grep_files_matchglob.test.ts (+1 test-defect fix the same evening: marker existed only in big_lines.txt).find_replace_all hardcoded 5,000-line cap now parameterized; grep_files.exclude/include glob contract aligned. +7 regression tests; user confirmed all build & tests OK.// before
.filter(e => e.key.startsWith('memory_'))
// after
.filter(e => typeof e.key === 'string' && e.key.startsWith('memory_'))
manage_projects absorbs registry reads (info / `listupdateSessionIndex silent skip for orphaned per-copy legacy inregister_project โ manage_projects, with uweb_search zero-result fallback fix (dead engine no lonripgrep promoted to runtime dependencpattern_scan ripgrep phase-1 candidate prefilter (B')get_memory local-file parse guard (hotfix)grep_files ripgrep-backed regex engine with JS fallback (Option AexecutedTool transparency stamp in the toolsProvider wrapper (silent-supattern_scan tool + puppeteer connected property-read fix + deagrep_files completion telemetry (live-verified same day; closed countergrep_files (code-verified + LIVEmanageProjectsImpl() gains 3 read actions: info (โ getProjectByPath; not-found error text unchanged), list (โ getAllProjects; F1' self-describing empty result carried over verbatim), search (โ search(query, maxResults); default 10 kept; F1' diagnostics attached only when status โ has_projects โ logic moved from tool body into impl without semantic change).manage_projects schema: enum extended to register|unregister|update|clear_all|info|list|search; new optional params query + max_results (1โ50, default 10); working_dir_path now documented as required for register/unregister/update/info; description restructured into MUTATIONS/READS sections โ consent contract preserved. One stale hint updated (unregister's "use list_projects/search_projects" โ action=list/action=search).get_project_infoโinfo, list_projectsโlist, search_projectsโsearch. All delegate to the shared impl and preserve LEGACY response shapes exactly (bare project object / {projects} ยฑ registry diagnostics; no action key), so existing callers/prompts keep working.toolPriority.ts untouched โ none of the four tools has a tier entry there (scan-verified).register_project(...).lmstudio/production.jsmanage_projects tool (src/tools/contextManagementTools.ts) โ flat params + per-action validation (no zod discriminated union, by design): action=register|unregister|update|clear_all; destructive actions require confirm=true. Shared implementation closure; the deprecated register_project alias delegates to it.ProjectRegistryManager.unregisterProject() โ removes an entry via canonical path match (F1 pattern) and records the path in a new bounded unregistered tombstone list (UNREGISTERED_TOMBSTONE_MAX = 50, newest-at-end). _syncFromSessionMemory() now skips tombstoned paths, so stale cross-stamped memory entries can no longer resurrect explicitly removed projects.ProjectRegistryManager.updateProject() โ rename and/or sourceDirs replacement for an EXISTING registration only (never creates; unknown path โ {updated:false}).clearAll() PRESERVES tombstones (a full wipe must not resurrect everything in one sweep). Backward compatible: pre-MANAGE registry files simply lack the field.manage_projects entry + deprecation note on register_project).src/workingDir.ts): restoreLastActiveProjectCwd() now runs bootstrapRegistryFromSessionMemory() BEFORE the idempotent guard, on every boot. The 11.09 bootstrap call sat BELOW the guard and was therefore only reachable when persisted CWD state was missing โ exactly the post-reinstall hazard (valid stale state โ early return โ empty registry for the whole session; observed live 12.09: both tools returned [] although durable stamps existed in plugin-dir + project msgpack stores). Bootstrap is self-conservative: no-op when EITHER registry source yields โฅ1 project, materializes only directories that still exist on disk (no ghosts, no auto-registration โ index.ts rule #2 intact); healthy-case cost = two small JSON reads per boot.project_registry.json (+.bak) relocated from the plugin dir to LM Studio's persistent data dir %USERPROFILE%\.lmstudio\extensions\data\crunch3r\ai-toolbox\ โ survives every lms dev --install wipe. New module src/dataDir.ts (getDataDir(), jest-guarded). Writers: boot-time bootstrap (workingDir.ts) + ProjectRegistryManager (contextManagementTools.ts) now target the data-dir primary with atomic tmp+rename (Windows file-lock fallback); plugin-dir copies kept as read-fallback for the migration window. Readers: listRegisteredProjects() priority = data-dir registry โ plugin-dir registry โ legacy .session_index.json.jest.config.cjs): REG-MOVE's new ../dataDir.js import crashed contextSearch.test.ts ("Cannot find module ../dataDir.js") โ the inserted mapper entry was double-escaped and matched NEITHER spec form. Both dataDir entries (double-dot + single-dot) regenerated byte-for-byte from known-good siblings, verified via require(config)+RegExp.test against every specifier form (hand-typed escapes corrupt across JSON/tool layers).src/workingDir.ts + tests): test-hygiene leak โ workingDir.test.ts's setWorkingDir(tmpdir) persisted fake working dirs into the REAL <repoRoot>/.ai_toolbox_state.json. State-file location is now jest-guarded to a per-run temp dir, resolved LAZY and mock-safe via new exported seam getStateFilePath() (one location per process; try/catch around os.tmpdir with fs.mkdtempSync('') fallback + pid-suffixed last resort that never touches the production file) โ module-scope side effects eliminated. First attempt (FIX #31, module-scope mkdtemp) broke 2 suites: utilityTools.test.ts crashed at setup ("os.tmpdir is not a function" โ its full jest.mock('os') factory lacked tmpdir), cwdConsistency failed against its hardcoded repo-root path. FIX #31b remixed both: utilityTools' 'os' mock gained tmpdir; cwdConsistency asserts against the module-owned path (imports getStateFilePath()). Rule for future on-disk-state assertions: import getStateFilePath() โ never re-derive. Production path unchanged.tests/registryDataDirMove.test.ts + tests/projectRegistryStatusMigration.test.ts; badge updated to 772 tests / 48 suites (badge count cross-confirmed by jest's own "48 total" line).src/tools/toolGatingProfile.ts (~320 LOC): user-level sparse profile at %USERPROFILE%\.ai_toolbox\tool_gating_profile.json โ outside the plugin dir on purpose, so it survives reinstalls/updates; written via shared atomicWriteFile (temp + rename โ crash-proof); missing/corrupt file degrades to empty profile (defaults apply), never breaks registration.syncToolGatingProfile) applied in toolsProvider.ts: โ RECONCILE โ only a live NON-default value that differs from storage is an unambiguous re-toggle and beats sticky storage; default-valued differences (fresh-chat fallback vs explicit revert are byte-indistinguishable per the SDK's per-chat scope) keep the stored value. โก OVERLAY โ all other stored values win over host defaults in new chats. โข FINALIZE โ storage rebuilt from final config, keys at default evicted (sparse), persisted exactly once on change (no steady-state disk I/O); cache ends equal to disk.toolsProvider.ts): fire-and-forget capture + drain before return; .catch(err => {โฆ; return false}).then(() => undefined) keeps the chain Promise<void>-typed WITHOUT a cast (user-caught TS2322 fixed per session rule โ no-cast), wrapped in try/catch so synchronous throws (e.g. path resolution) can never break tool registration. GOD MODE untouched: capture is independent of gating, and godMode=true itself is captured like any other toggle.tests/toolGatingProfile.test.ts (+12 cases; 46 suites total); two round-2 root causes fixed during gate runs: (a) a case assumed webSearch default=false โ schema default is TRUE, so the scenario was flipped to a truly non-default seed ({webSearch:true}, live=false), module behavior proven correct by hand-trace of reconcile/overlay/finalize; (b) cross-test sticky-toggle leakage via the pid-scoped redirect file โ hermetic beforeEach (resetGatingProfileCache() + unlink of redirected profile path).results.length === 0anyEngineEmpty flag distinguishes the final error wording when all engines responded but yielded nothing parseable ("No search results found โ engines responded but returned no parseable results (possibly bot-blocked)") from hard total failure ("All search engines failed"). The <2 sparse-but-real success threshold is unchanged.describe 'web_search zero-result fallback' block in tests/webResearchTools.test.ts โ mid-chain shell pages must not stop the chain (expect engine=bing, count=2, 3 fetch calls) + all-empty โ new wording. Suite 11/11 PASS (9 pre-existing unmodified + 2 new), user-run locally; ESLint 0 problems, tsc clean.src/tools/patternScan.ts (RIPGREP-PHASE-1 block) โ searchCandidates from the shared module (../utils/ripgrepEngine.js, same import identity as fileSystemTools; lazy dynamic import, never breaks boot). Inputs: absolute root, raw trimmed pattern, mode 'literal' iff explicit literal or demoted (else 'regex'), caseInsensitive = !(options.caseSensitive ?? true) โ SENSITIVE default, deliberately NOT mirroring grep_files' hardcoded -i; exclude globs = DEFAULT_EXCLUDE_DIRS only (user globs keep this module's custom semantics in the JS filter); maxDepth for dir roots. Candidate paths are normalized from BOTH rg output formats (absolute and root-relative). On 'ok': named targets go to the workers; every non-named target still passes stat + size gate + a Buffer newline-count read, so 'size'/'line-cap' skip records AND stats.filesScanned stay byte-identical to the full walk (increment at the worker's exact position). ANY non-ok status (no-matches, fallback-required, throw) โ full pre-B' pipeline; no short-circuit even on a clean negative.'binary' skip record for a file rg proved pattern-absent โ binary detection needs content inspection, and such a file is unobservable in every output field except skipped[].tests/patternScanBPrime.test.ts (liveness-gated live battery + unconditional dep-absent reference tests; unique BPRIME_* tmpdir fixtures, TARGET_COUNT=7). Existing tests/patternScan.test.ts binary test rewritten tolerant-style: accepts 'binary' record (fallback regime) OR absent record (live regime), still fails on any third outcome โ a documented divergence, not a silent weakening.patternScan.ts header corrected (the "no dependency on any other tool module" claim is superseded by the ripgrepEngine import) + TOOLS_REFERENCE.md pattern_scan entry gained the B' section with the divergence note.B\'src/utils/ripgrepEngine.ts (~250 LOC) โ self-contained candidate-file filter: lazy dynamic import('ripgrep') (pithings/ripgrep-node 0.3.1, ESM-only WASM build of rg) on first use only โ mirrors the FIX-HANG-5 lazy-load discipline, so a missing/broken dep never breaks plugin boot; exit-code mapping per the package's documented contract (0 โ ok+files ยท 1 โ no-matches clean negative ยท 2 โ fallback-required, incl. Rust-dialect parse errors such as lookarounds/backreferences, detected at compile time in ~2โ3 ms); flag mirroring of production walker semantics (--no-ignore --no-require-git --hidden; -i passed as a parameter because grep_files compiles every regex with 'i'; caller-supplied exclusion globs; --max-depth=cap+1 depth-budget parity quirk, unit-asserted โ cap 0 โ no flag); no --max-filesize (exact-byte size skip records stay phase-2-only). Contract: the function never throws โ every failure mode resolves to a typed fallback signal.src/tools/fileSystemTools.ts (regex-mode + directory target only) โ two-phase split: phase 1 awaits the engine before scan start (its cost sits outside the 15 s GREP_SCAN_DEADLINE_MS window) and builds an allow-set of rg-named candidates; phase 2 runs the EXISTING processWithRegex shaping unchanged on that set (line-split, >20k-char line gate, .trim(), truncation + 'โฆ', 1-based line numbers, include_context, result caps). Single-file targets and AST mode are byte-for-byte untouched paths; any phase-1 non-'ok' outcome leaves the candidate set null โ full-JS walk runs exactly as before, every existing hang guard intact (15 s deadline, per-regex 500 ms abandon-and-continue, worker isolation + 2 s kill for ReDoS-suspect patterns, 30 s fallback timeout, wall-clock backstop).-i always applies in regex mode (existing production semantics, now explicit); rg --hidden mirrors the walker's dot-dir scanning whenever no include pattern is given; the >20k-char line gate and all caps/skip-record contracts are preserved verbatim.max_file_size or over the max_lines cap is reported in skipped_files with byte/line counts identical to the pre-swap records โ pinned by grep_files_hang_backstop, grepFilesParity and the golden baseline.tests/ripgrepEngine.test.ts (incl. real-WASM integration, skipIf-guarded when the dep is absent) + characterization battery tests/grepFilesParity.test.ts against frozen golden baseline tests/fixtures/grepFilesBaseline.json generated BEFORE any src change; AST-mode smoke coverage closed in the same phase.moduleNameMapper rule (^\.\/utils/(.*)\.js$) matched ANY utils require and broke the @babel/* CJS packages (they ship lib/ with .js-suffixed requires) โ replaced by per-file exact mapper entries only.matches: [], filesScanned: 0); fixed by relativizing every candidate against targetDir before allow-set construction (paths outside the tree keep normalized absolute form and can never match โ safe no-op).
Round 3: sole failure 705/706 โ RC-E (round-trip triage): with the rg prefilter 'ok', processFile early-returned non-named files BEFORE both gates, so non-matching over-cap files were silently absent from skipped_files. Fixed in fileSystemTools.ts ONLY: stat + size gate first, then a branch for prefilter-active non-named files โ Buffer read + newline-byte count (lineCount = newlines + 1, exact split('\n') semantics) โ byte-identical line-cap skip record; named-candidate and fallback paths unchanged. Perf trade-off documented honestly: the probe restores pre-swap I/O for gate-passing non-matching files โ the remaining ripgrep win is per-line .test() CPU + ReDoS exposure on the inline path (the spike's ~22ร figure predates parity restoration on an uncontrolled tree, so docs state speed qualitatively).
Round 4: ALL PASS โ typecheck 0 errors / full jest green / build success; parity re-run vs golden baseline = zero unexplained diffs (no one-time baseline special case triggered).src/utils/simulation.ts (self-exec dev script), src/toolsDocumentation.ts, src/tools/imageAnalysisTools.ts (superseded by live imageProcessingTools.ts), src/tools/backupUtils.ts, src/tools/toolProtocolWarnings.tssrc/tools/executionRegistry.ts, src/tools/utilityRegistry.ts (duplicate of live 3-arg registerUtilityTools in utilityTools.ts; no jest mapper targeted them)rules/{deadCodeDetection,asyncModernizer,typeInference,modulePathNormalization}.ts โ engine (recodeEngine.ts), types (recodeTypes.ts) and the LIVE rule rules/unusedImports.ts verified untouched in the same directory listingssrc/toolsProvider.ts.bak, tests/executedToolTransparency.test.ts.baksrc/tools/TokenStatsManager.recordToolResult) still fires exactly once per successful call โ with the ORIGINAL (unstamped) payload and the SAME ground-truth name the stamp now carries.pattern_scan tool (src/tools/fileSystemTools.ts; clean-room engine in new module src/tools/patternScan.ts) โ recursive content search returning matching lines as {file, line, content}. Diff vs grep_files: unsafe/syntactically-invalid regexes fail fast and are auto-demoted to literal mode (reported via demotedToLiteral); fully async with bounded concurrency (concurrency 1โ16, default 4). Hard caps: per-file size gate 256 KB + line-cap gate 10,000 lines (oversize/over-line files reported in skipped[], never scanned), maxMatchesPerFile default 50, global cap 200 (stats.truncated=true when hit); matchLineLength truncation default 300 chars; root accepts a directory or single file, relative roots resolve against the plugin working directory. Jest mock tests/__mocks__/patternScan.ts + mapper entry in jest.config.cjs; full suite green post-wiring (657/657 tests, 38 suites โ user-verified); live probe: visible in running-plugin tool list, stress-probed against the 12.7 MB / 296k-line dist bundle (user-declared SUCCESS).connected property-read fix (src/tools/browserAutomationTools.ts + src/types.d.ts) โ puppeteer 24 exposes Browser.connected at runtime as a getter property, not a method (installed 24.43.1); the local module augmentation now declares it readonly connected: boolean and session-liveness checks read it as a property. Live probe passed same window (browser_open_page โ screenshot save, PNG magic-verified).src/browserAutomationTools.ts (zero imports) deleted behind verified backup ai_toolbox-pre-deadfile-delete-20260831.zip in .ai_toolbox_backups/; typecheck + full jest green post-deletion (user-confirmed). Housekeeping: seven stale .bak files project-wide deleted and re-verified zero.pattern_scan entries added, File System count 22โ23, unique-tool totals 130โ131, Git & GitHub table count corrected to the code's 15; dead-file tree reference removed; screenshot_desktop write lines re-attributed from browserAutomationTools.ts to imageProcessingTools.ts (platform-native subprocess writes the file directly โ no Node-side write; its only atomic-write path is attachment temp-file materialization via resolveAttachmentFile).patternNeedsWorkerIsolation() triage gate: STRICTER than isSafeRegex (which misses brace-bounded nested quantifiers, backreferences, deeply nested groups). Anything the gate cannot PROVE cheap is evaluated for that whole file inside a node:worker_threads Worker (single round-trip; hard-killed via worker.terminate() after WORKER_KILL_MS = 2000 ms). Safe patterns keep the inline fast path โ zero overhead on the common case. Kill/failure โ file recorded in skipped_files, scan continues.$-anchored check let ((a+){3}){4}x defeat triage (T1b double-freeze root cause). Any valid {n[,m]} on an unbounded +/* group body now routes to the worker at any position.self.onmessage) to Node contract (parentPort direct payload) โ the old shape threw self is not defined at boot, so every risky pattern "crashed" and was misreported as a 2000 ms kill (zero regex work). Verified offline in this runtime: safe pattern returns exact indices; T1b-exact catastrophic payload hard-killed at 2000 ms.refactor_code/Recode Engine (only AST-based refactoring in field), AutoTracker + ContextGuard session/token management (mid-loop token deltas firing 75%/90% thresholds inside long tool chains, automatic checkpoint summarization & compression โ absent from every surveyed plugin beyond basic memory CRUD), hang-safe grep_files/find_replace_all, guarded line_operations (MD5 post-write check), background-command suite, browser automation with persistent sessions, integrated multi-format RAG (PDF/DOCX/XLSX + web extraction), run_tests (auto-detects Jest/Mocha/Vitest), planning state machine (create_plan family), secret_scan, data visualization via generate_chart (zero data-viz plugins in field), cross-project memory registry, and the backup/restore suite.grep_files entry corrected to the current contract โ added missing params max_depth (default 10, range 1โ50) and max_lines (default 5000); documented deadline behavior (partial results + aborted: true, per v1.9.9 hard limits) and REV-24 prose-alternation handling (patternMode:"auto_escaped" hints)..bak backups created for both files before editing (pending post-commit cleanup sweep alongside README.md.bak).src/security.ts (isSafeRegex, ~L92โ100): bare & removed from clause 1's char class โ /([*+?]/. Clause 2 (code-signature detection) intentionally kept, so C++-style searches containing & are still auto-escaped โ but only when paired with a genuine unescaped *, + or ?. Inline comment documents the rationale (REV-24).src/tools/fileSystemTools.ts (~L2462/2491): inline heuristic aligned to the same char class; double-quoted hint strings added at the forced-literal sites so a future literal-mode decision is explained to the caller instead of failing silently (mid-session string-quoting compile defect on these lines was caught by user's tsc and fixed, line-level compile-proven).patternMode:"literal", 0 matches) โ forensics proved the active process was executing a stale installed copy (its src/security.ts mtime 21.08 = pre-fix). Both fixed source files synced into the install (C:\Users\root.MPITS\.lmstudio\extensions\plugins\crunch3r\ai-toolbox\src\, marker verified), then: grep_files("Backup & Restore|Git & GitHub", TOOLS_REFERENCE.md) โ patternMode:"regex" + exactly 4 matches (L15/L25 overview rows, L220/L495 headings). Incident closed._rebuildKeysCache() now merges into the existing key set instead of replacing it, preserving entries observed pre-rebuild.saveMemoryFile's _origin handling made conditional so a re-saved file is not falsely flagged as fresh-start data (which triggered the overwrite path).src/tools/vectorRagTools.ts: 3-line termination guarantee in all three chunkers โ startIndex = Math.max(endIndex, startIndex + 1) โ strict forward progress on every iteration. No API/behavior change for well-formed inputs; the stall case now simply emits its final partial chunk and stops.tests/vectorRagTools.ragWebContent.test.ts: regression spec (oversized page) covers the poison-remainder path end-to-end; heading assertion made case-insensitive (html-to-text uppercases <h*> headings by default โ test expectation aligned with library behavior, no source change).tests/webResearchTools.test.ts (test infra only): beforeEach mock isolation fixed โ mockReset() + explicit re-application of base values for the duck-duck-scrape search and performanceUtils.fetchWithRetry mocks. Closed the last failing test, proven to be cross-test order contamination (single-test run with -t passes 1/1; full suite failed without this).src/tools/networkToolsRegistry.ts + its Jest mock tests/__mocks__/networkToolsRegistry.ts deleted. Certainty: zero references in src/, tests/, scripts/; tsup bundles from src/index.ts only (file never shipped); no jest mapper entry for it; both changelog entries of today already tracked its deletion as backlog. The dead file held an unbounded response.text() rag_web_content โ re-wiring risk eliminated.rag_web_content (tools/vectorRagTools.ts):
readCappedText: budget 500_000 โ 250_000 chars; oversized pages now return success:true + truncated:true with usable partial chunks instead of a hard "Page too large" failure after consuming the full allocation (same pattern as the search engines).htmlToText() pass โ previously raw HTML was chunked as "text" (tag soup scored into embeddings; ~40โ60% of budget consumed by markup; bestMatch.text returned unreadable HTML to the LLM).chunks: [{text, score, metadata}]) + truncated flag; bestMatch key preserved (now = topChunks[0]) for backward compatibility. Peak transient allocation per call drops from ~5โ8 MB to ~2โ3 MB.wikipedia_search (tools/webResearchTools.ts): last unbounded read in the web-research path bounded โ await response.json() โ JSON.parse(await readBoundedText(response, 200_000)).<success:truetruncated:trueRAG search failed: โฆhtml-to-texttests/webResearchTools.test.ts: shared fetch mock's default body switched from HTML to a JSON stream (required by change 3; all other assertions unaffected โ htmlToText stays mocked, oversized-page regression tests use their own dedicated mocks).[HEAP-GUARD]src/performanceUtils.ts: new readCappedText(response, maxChars) โ soft cap, stops reading at budget + cancels socket, returns partial content (a truncated search page still yields its top results), never throws on size. Plus checkHeapPressure(toolName) watchdog: pre-call heap probe; logs one [HEAP-GUARD] โ ๏ธ N MB BEFORE "<tool>" started line when usage crosses 1 GB โ if another OOM happens, this names the suspect call in the log immediately before the crash.tools/webResearchTools.ts: all three fallback engines (searchDDGFetch, searchGoogle, searchBing) now use readCappedText(โฆ, MAX_SEARCH_HTML_CHARS = 300_000) instead of unbounded .text(). Worst-case allocation per engine run is now bounded.tools/httpClientTools.ts: all five response-body reads in http_request / http_get_json / http_post_json now go through readBoundedText(โฆ, MAX_HTTP_BODY_CHARS = 500_000) (JSON parsed from the bounded text; oversized bodies fail loudly like fetch_web_content).toolsProvider.ts: wrapper calls checkHeapPressure() before every tool execution.tools/vectorRagTools.tsfetch()fetchWithRetry paths had no per-attempt timeout โ slow/stalled transfers held buffers indefinitely.src/performanceUtils.ts): readBoundedText(response, maxChars) (Content-Length fast-reject with zero reads + streaming chunked read that enforces the budget and cancels the socket early) and WEB_FETCH_TIMEOUT_MS = 30_000; fetchWithRetry now bounds every attempt via AbortController timeout (caller-supplied signals respected).fetch_web_content: 50,000-char cap enforced DURING transfer; error contract preserved (Page too large (โฆ) โฆ Use searxng_search + summary_only).rag_web_content: new 500,000-char budget + shared timeout/backoff helper; error contract preserved (RAG search failed: โฆ).tests/webResearchTools.test.ts (suite oversized page protection): Content-Length fast path asserts zero chunks read + socket cancelled; chunked over-cap stream aborts after exactly 2 of 3 chunks.rag_web_content registration in LM Studio's tool list. Root cause: the vectorRAG registry (tools/vectorRagTools.ts) and the webSearch registry (tools/webResearchTools.ts) both registered a tool with that name, and toolsProvider.ts pushes all registry output without any name-dedup โ duplicate UI entry + non-deterministic dispatch between two different implementations. The keyword "placeholder" implementation (50 KB cap, top-5 sentence filter) was removed from webResearchTools.ts (tool block + now-unused RagWebContentParams interface); the tool is now provided exclusively by the real-RAG version in vectorRagTools.ts.
Verification: user rebuild + reinstall โ UI shows exactly one entry; bundle grep = exactly 1ร name:"rag_web_content" each (dist/index.js L290769, dist/index.mjs L288744); size deltas โ823 B / โ804 B vs. the accepted v1.9.9 build. No tests touched (suite has no assertion on the removed block; count check stays true).
Note: a third definition exists in dead code tools/networkToolsRegistry.ts (orphan file, zero imports โ verified) โ its removal is tracked as backlog, not part of this fix.fileSystemTools.ts, sync-regex path): the per-file line-cap skip message used console.warn โ stderr โ displayed as [ERROR] in LM Studio dev logs despite being an expected, informational event (it is already reported to the caller via skipped_files). Changed to console.log ([INFO]). Genuine anomaly warnings elsewhere were intentionally left untouched.matchGlob anchored the regex before its escape pass โ every include pattern matched 0 files silently. Fixed.max_lines; skipped files reported in skipped_files. New regression suite tests/grep_files_matchglob.test.ts (+1 test-defect fix the same evening: marker existed only in big_lines.txt).find_replace_all hardcoded 5,000-line cap now parameterized; grep_files.exclude/include glob contract aligned. +7 regression tests; user confirmed all build & tests OK.// before
.filter(e => e.key.startsWith('memory_'))
// after
.filter(e => typeof e.key === 'string' && e.key.startsWith('memory_'))