dsh-local-models 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-es.md +1 -1
- package/README-hi.md +1 -1
- package/README-pt.md +1 -1
- package/README-zh.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +14 -2
- package/lib/index.js +56 -28
- package/package.json +1 -1
- package/skills/dsh-local-models-ops/SKILL.md +19 -18
package/README-es.md
CHANGED
|
@@ -67,7 +67,7 @@ dsh plugin --profile web remove dsh-local-models
|
|
|
67
67
|
|
|
68
68
|
## Usage
|
|
69
69
|
|
|
70
|
-
1. **Choose GGUF…** — elige un archivo de modelo (
|
|
70
|
+
1. **Choose GGUF…** — elige un archivo de modelo (botones Home / Root, atajos Models, navegación con Up). Up sube hasta `/`, así que un modelo en un disco montado (`/mnt/…`, `/media/…`) se abre sin registrar antes su carpeta.
|
|
71
71
|
2. Ajusta **context**, **KV cache K / V**, **Max MTP head** (borrador fijo, 0-7; 3 es el punto óptimo ajustado — más profundidad colapsa con ctx grandes), **thinking level** + casilla **preserve thinking**, y los ajustes opcionales de **mmproj** y **MoE**.
|
|
72
72
|
3. **Load model**, observa la tarjeta de estado, inspecciona la salida con **Open terminal**.
|
|
73
73
|
4. **Register in dsh** — la ruta (por defecto `local-<alias>`) aparece en el selector de modelos.
|
package/README-hi.md
CHANGED
|
@@ -53,7 +53,7 @@ dsh plugin --profile web remove dsh-local-models
|
|
|
53
53
|
|
|
54
54
|
## Usage
|
|
55
55
|
|
|
56
|
-
1. **Choose GGUF…** — कोई model फ़ाइल चुनें (Home / Models shortcuts, Up navigation)।
|
|
56
|
+
1. **Choose GGUF…** — कोई model फ़ाइल चुनें (Home / Root buttons, Models shortcuts, Up navigation)। Up सीधे `/` तक जाता है, इसलिए mounted disk पर रखा model (`/mnt/…`, `/media/…`) बिना folder दर्ज किए खुल जाता है।
|
|
57
57
|
2. **context**, **KV cache K / V**, **Max MTP head** (fixed draft, 0-7; 3 ही ट्यून किया गया sweet spot है — इससे गहरा draft बड़े ctx पर collapse हो जाता है), **thinking level** + **preserve thinking** checkbox, वैकल्पिक **mmproj** और **MoE** settings ट्यून करें।
|
|
58
58
|
3. **Load model**, status card देखें, **Open terminal** से output जाँचें।
|
|
59
59
|
4. **Register in dsh** — route (default `local-<alias>`) Models picker में दिखने लगता है।
|
package/README-pt.md
CHANGED
|
@@ -67,7 +67,7 @@ dsh plugin --profile web remove dsh-local-models
|
|
|
67
67
|
|
|
68
68
|
## Usage
|
|
69
69
|
|
|
70
|
-
1. **Choose GGUF…** — escolha um arquivo de modelo (
|
|
70
|
+
1. **Choose GGUF…** — escolha um arquivo de modelo (botões Home / Root, atalhos Models, navegação Up). O Up sobe até `/`, então um modelo em um disco montado (`/mnt/…`, `/media/…`) abre sem cadastrar a pasta antes.
|
|
71
71
|
2. Ajuste **context**, **KV cache K / V**, **Max MTP head** (draft fixo, 0-7; 3 é o ponto ideal ajustado — profundidades maiores colapsam em ctx grande), **thinking level** + checkbox **preserve thinking**, e as configurações opcionais de **mmproj** e **MoE**.
|
|
72
72
|
3. **Load model**, acompanhe o card de status e inspecione a saída via **Open terminal**.
|
|
73
73
|
4. **Register in dsh** — a rota (padrão `local-<alias>`) aparece no seletor de modelos.
|
package/README-zh.md
CHANGED
|
@@ -67,7 +67,7 @@ dsh plugin --profile web remove dsh-local-models
|
|
|
67
67
|
|
|
68
68
|
## Usage
|
|
69
69
|
|
|
70
|
-
1. **Choose GGUF…** —— 选择模型文件(Home / Models 快捷入口,Up 向上导航)。
|
|
70
|
+
1. **Choose GGUF…** —— 选择模型文件(Home / Root 按钮,Models 快捷入口,Up 向上导航)。Up 可以一直走到 `/`,因此挂载磁盘上的模型(`/mnt/…`、`/media/…`)无需先登记文件夹即可打开。
|
|
71
71
|
2. 调整 **context**、**KV cache K / V**、**Max MTP head**(固定草稿,0-7;3 是调校好的甜点值 —— 更深的草稿在大 ctx 下会崩溃)、**thinking level** + **preserve thinking** 复选框,以及可选的 **mmproj** 和 **MoE** 设置。
|
|
72
72
|
3. **Load model**,观察状态卡片,通过 **Open terminal** 查看输出。
|
|
73
73
|
4. **Register in dsh** —— 该路由(默认 `local-<alias>`)会出现在 Models 选择器中。
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ dsh plugin --profile web remove dsh-local-models
|
|
|
69
69
|
|
|
70
70
|
## Usage
|
|
71
71
|
|
|
72
|
-
1. **Choose GGUF…** — pick a model file (Home / Models shortcuts, Up navigation).
|
|
72
|
+
1. **Choose GGUF…** — pick a model file (Home / Root buttons, Models shortcuts, Up navigation). Up goes all the way to `/`, so a model on a mounted disk (`/mnt/…`, `/media/…`, a network export) opens without adding its folder to Model folders first.
|
|
73
73
|
2. Tune **context**, **KV cache K / V**, **Max MTP head** (fixed draft, 0-7; 3 is the tuned sweet spot — deeper collapses at large ctx), **thinking level** + **preserve thinking** checkbox, optional **mmproj** and **MoE** settings.
|
|
74
74
|
3. **Load model**, watch the status card, inspect output via **Open terminal**.
|
|
75
75
|
4. **Register in dsh** — the route (default `local-<alias>`) appears in the Models picker.
|
package/lib/client.js
CHANGED
|
@@ -326,6 +326,11 @@ window.__ModuleLoader__.load({
|
|
|
326
326
|
var err = errPair[0], setErr = errPair[1];
|
|
327
327
|
var selPair = useStatePair(null);
|
|
328
328
|
var selected = selPair[0], setSelected = selPair[1];
|
|
329
|
+
// The server stops a huge folder at 2000 entries. Root browsing
|
|
330
|
+
// makes /proc and /usr/lib reachable, so say so rather than let the
|
|
331
|
+
// list look complete when it is not.
|
|
332
|
+
var truncPair = useStatePair(false);
|
|
333
|
+
var truncated = truncPair[0], setTrunc = truncPair[1];
|
|
329
334
|
// Remembered folders (persisted): jump buttons + toggle for the current dir.
|
|
330
335
|
var LS_FOLDERS = "dsh-local-models:remembered-folders";
|
|
331
336
|
var readFolders = function () {
|
|
@@ -355,6 +360,10 @@ window.__ModuleLoader__.load({
|
|
|
355
360
|
// empty-looking folder reads as a broken picker.
|
|
356
361
|
var dirMode = props.dirMode === true;
|
|
357
362
|
var allFiles = props.allFiles === true || dirMode;
|
|
363
|
+
// The listing starts at the filesystem root, so the naive
|
|
364
|
+
// base + "/" + name would ask for "//etc" and paint a path bar
|
|
365
|
+
// reading "//etc", with every deeper click stacking a slash.
|
|
366
|
+
var into = function (base, name) { return (base === "/" ? "" : base) + "/" + name; };
|
|
358
367
|
var load = useCallback(function (d) {
|
|
359
368
|
setLoading(true);
|
|
360
369
|
setErr(null);
|
|
@@ -364,6 +373,7 @@ window.__ModuleLoader__.load({
|
|
|
364
373
|
setDir(body.path || d);
|
|
365
374
|
setEntries(body.entries);
|
|
366
375
|
setParent(body.parent != null ? body.parent : null);
|
|
376
|
+
setTrunc(Boolean(body.truncated));
|
|
367
377
|
setSelected(null);
|
|
368
378
|
} else {
|
|
369
379
|
setErr((body && body.error) || "could not list directory");
|
|
@@ -387,6 +397,7 @@ window.__ModuleLoader__.load({
|
|
|
387
397
|
"div",
|
|
388
398
|
{ style: style.row },
|
|
389
399
|
h("button", { style: style.secondaryButton, onClick: function () { load(""); } }, "Home"),
|
|
400
|
+
h("button", { style: style.secondaryButton, title: "the filesystem root — mounted disks hang off it (/mnt, /media, /run/media)", onClick: function () { load("/"); } }, "Root"),
|
|
390
401
|
(props.shortcuts || []).map(function (sc, i) {
|
|
391
402
|
return h("button", { key: i, style: style.secondaryButton, onClick: function () { load(sc.path); }, title: sc.path }, sc.label);
|
|
392
403
|
}),
|
|
@@ -403,7 +414,7 @@ window.__ModuleLoader__.load({
|
|
|
403
414
|
{ style: style.list },
|
|
404
415
|
(entries || []).map(function (en, i) {
|
|
405
416
|
if (en.type === "dir") {
|
|
406
|
-
return h("li", { key: i, style: Object.assign({}, style.item, style.itemDir), onClick: function () { load(dir
|
|
417
|
+
return h("li", { key: i, style: Object.assign({}, style.item, style.itemDir), onClick: function () { load(into(dir, en.name)); } }, en.name + "/");
|
|
407
418
|
}
|
|
408
419
|
var sel = selected === en.name;
|
|
409
420
|
return h("li", {
|
|
@@ -414,13 +425,14 @@ window.__ModuleLoader__.load({
|
|
|
414
425
|
}, en.name + (en.size != null ? " (" + fmtGiB(en.size) + ")" : ""));
|
|
415
426
|
}),
|
|
416
427
|
),
|
|
428
|
+
truncated ? h("p", { style: style.warn }, "showing the first 2000 entries — open a subfolder to narrow it down") : null,
|
|
417
429
|
h(
|
|
418
430
|
"div",
|
|
419
431
|
{ style: style.row },
|
|
420
432
|
h("button", {
|
|
421
433
|
style: (dirMode ? dir : selected) ? style.button : Object.assign({}, style.button, style.disabled),
|
|
422
434
|
disabled: dirMode ? !dir : !selected,
|
|
423
|
-
onClick: function () { if (dirMode) { if (dir) props.onPick(dir); } else if (selected) props.onPick(dir
|
|
435
|
+
onClick: function () { if (dirMode) { if (dir) props.onPick(dir); } else if (selected) props.onPick(into(dir, selected)); },
|
|
424
436
|
}, dirMode ? "Use this folder" : "Use this file"),
|
|
425
437
|
h("button", { style: style.secondaryButton, onClick: props.onClose }, "Cancel"),
|
|
426
438
|
),
|
package/lib/index.js
CHANGED
|
@@ -100,11 +100,14 @@ const MMPROJ_CPU = process.env.LOCAL_MODELS_MMPROJ_CPU !== "0";
|
|
|
100
100
|
* above the raw threshold) — and with xhigh + preserveThinking every turn
|
|
101
101
|
* adds 10-20K thinking tokens, so compaction fires again on the next step:
|
|
102
102
|
* the "compaction loop". The honest fix for mid-size windows is a per-model
|
|
103
|
-
* headroom override
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
103
|
+
* headroom override on the agent-preset row that mounts the running engine
|
|
104
|
+
* (`preset-standard` → `plugins` → `compaction` group → `compaction-basic`;
|
|
105
|
+
* headroom ~6-16K moves the 131K threshold to ~82-92K) — a patch on the
|
|
106
|
+
* top-level `compaction-basic` row is dead config (that row is disabled),
|
|
107
|
+
* and a smaller O would truncate the xhigh thinking blocks this cap
|
|
108
|
+
* exists to protect. Heavy xhigh thinking that needs more must raise
|
|
109
|
+
* per-request maxTokens explicitly (which honestly moves the compaction
|
|
110
|
+
* threshold earlier instead of silently disabling it).
|
|
108
111
|
*/
|
|
109
112
|
const MAX_OUTPUT_TOKENS = 32768;
|
|
110
113
|
/** Default output share of the window: never offer more than half as max
|
|
@@ -1563,19 +1566,36 @@ function sendJson(res, code, body) {
|
|
|
1563
1566
|
res.end(payload);
|
|
1564
1567
|
}
|
|
1565
1568
|
|
|
1569
|
+
/** The filesystem root, in the form resolve() produces (posix "/"). Every
|
|
1570
|
+
* absolute path is under it, so isPathAllowed() treats it as containing the
|
|
1571
|
+
* whole tree — the one root that makes "Up" from any folder land somewhere
|
|
1572
|
+
* real. */
|
|
1573
|
+
const FILESYSTEM_ROOT = resolve(sep);
|
|
1574
|
+
|
|
1575
|
+
/** Single wording for a path the file endpoints refuse. With the root in
|
|
1576
|
+
* allowedRoots() this is unreachable for any absolute path; it stays as the
|
|
1577
|
+
* one place the wording lives, so narrowing the roots again has something to
|
|
1578
|
+
* narrow. */
|
|
1579
|
+
const NOT_READABLE = "this tab may not read that path";
|
|
1580
|
+
|
|
1566
1581
|
/** Roots the file endpoints (browse/gguf-meta/run, and profile saves) may
|
|
1567
|
-
* touch
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1582
|
+
* touch. The list LEADS WITH THE FILESYSTEM ROOT: the picker walks up to "/"
|
|
1583
|
+
* on purpose, so a model can live on a mounted disk the user never declared
|
|
1584
|
+
* here (/mnt/data, /media/<user>/<label>, a network export) and reaching it
|
|
1585
|
+
* must not depend on having typed its path into Model folders first.
|
|
1586
|
+
*
|
|
1587
|
+
* Everything below the root is therefore readable, which makes the remaining
|
|
1588
|
+
* entries — home, the shortcut dirs (Runtime card, else
|
|
1589
|
+
* LOCAL_MODELS_SHORTCUTS), the model dirs of saved profiles, the llama.cpp
|
|
1590
|
+
* dir — jump buttons and back-compat rather than a confinement. They stay
|
|
1591
|
+
* because the picker offers them, and because a saved profile must stay
|
|
1592
|
+
* loadable whatever else happens.
|
|
1573
1593
|
*
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1594
|
+
* The guard that is still load-bearing is isLlamaServerPath(): it, not this
|
|
1595
|
+
* list, decides what may be spawned (POST /settings, /runtime/check and
|
|
1596
|
+
* requireBin()), so nothing a browse opens can turn into the executable. */
|
|
1577
1597
|
function allowedRoots() {
|
|
1578
|
-
const roots = [homedir(), ...shortcutDirs().map((s) => s.path)];
|
|
1598
|
+
const roots = [FILESYSTEM_ROOT, homedir(), ...shortcutDirs().map((s) => s.path)];
|
|
1579
1599
|
try {
|
|
1580
1600
|
for (const profile of cachedProfiles()) {
|
|
1581
1601
|
for (const declared of [profile?.modelPath, profile?.mmprojPath]) {
|
|
@@ -1613,9 +1633,10 @@ function allowedRoots() {
|
|
|
1613
1633
|
return roots;
|
|
1614
1634
|
}
|
|
1615
1635
|
|
|
1616
|
-
/** A path we are willing to
|
|
1617
|
-
* segment, and
|
|
1618
|
-
*
|
|
1636
|
+
/** A path we are willing to show as a picker button: absolute, no ".."
|
|
1637
|
+
* segment, and naming one folder rather than the whole tree. The root is
|
|
1638
|
+
* browsable on its own (there is a Root button and an Up that reaches it), so
|
|
1639
|
+
* this is button hygiene, not confinement. Exported for tests. */
|
|
1619
1640
|
export function isSafeRootPath(p) {
|
|
1620
1641
|
if (typeof p !== "string" || !p.trim()) return false;
|
|
1621
1642
|
const raw = p.trim();
|
|
@@ -1641,7 +1662,9 @@ function isPathAllowed(p) {
|
|
|
1641
1662
|
} catch {
|
|
1642
1663
|
return false;
|
|
1643
1664
|
}
|
|
1644
|
-
|
|
1665
|
+
// The root needs its own clause: the prefix test below would only ever
|
|
1666
|
+
// match "/" itself (every other path starts "/…", not "//").
|
|
1667
|
+
return abs === r || r === FILESYSTEM_ROOT || abs.startsWith(r + sep);
|
|
1645
1668
|
});
|
|
1646
1669
|
}
|
|
1647
1670
|
|
|
@@ -1865,12 +1888,12 @@ function cachedProfiles() {
|
|
|
1865
1888
|
export function normalizeProfileConfig(raw) {
|
|
1866
1889
|
const modelPath = typeof raw?.modelPath === "string" && raw.modelPath ? raw.modelPath : "";
|
|
1867
1890
|
if (!modelPath || !existsSync(modelPath)) throw new Error("profile needs a modelPath that exists on disk");
|
|
1868
|
-
if (!isPathAllowed(modelPath)) throw new Error("modelPath
|
|
1891
|
+
if (!isPathAllowed(modelPath)) throw new Error("modelPath " + NOT_READABLE + ": " + modelPath);
|
|
1869
1892
|
const ctx = Number.isInteger(raw?.ctx) && raw.ctx > 0 ? raw.ctx : 8192;
|
|
1870
1893
|
const mtpHeads = Number.isInteger(raw?.mtpHeads) && raw.mtpHeads >= 0 ? Math.min(raw.mtpHeads, MTP_MAX) : 0;
|
|
1871
1894
|
const mmprojPath = typeof raw?.mmprojPath === "string" && raw.mmprojPath ? raw.mmprojPath : null;
|
|
1872
1895
|
if (mmprojPath && !existsSync(mmprojPath)) throw new Error("mmprojPath does not exist: " + mmprojPath);
|
|
1873
|
-
if (mmprojPath && !isPathAllowed(mmprojPath)) throw new Error("mmprojPath
|
|
1896
|
+
if (mmprojPath && !isPathAllowed(mmprojPath)) throw new Error("mmprojPath " + NOT_READABLE + ": " + mmprojPath);
|
|
1874
1897
|
const effort = normalizeEffort(raw?.effort);
|
|
1875
1898
|
const preserveThinking = normalizePreserveThinking(raw);
|
|
1876
1899
|
// Multi-GPU placement. Legacy profiles have neither field and keep
|
|
@@ -2085,11 +2108,16 @@ export function apply(ctx) {
|
|
|
2085
2108
|
const url = new URL(req.url ?? "/", "http://x");
|
|
2086
2109
|
const dir = url.searchParams.get("dir");
|
|
2087
2110
|
const allFiles = url.searchParams.get("all") === "1";
|
|
2088
|
-
const
|
|
2111
|
+
const raw = dir && dir.indexOf("/") === 0
|
|
2089
2112
|
? dir
|
|
2090
2113
|
: dir ? join(homedir(), dir) : homedir();
|
|
2114
|
+
// Canonical before anything else. The picker joins with "/" and
|
|
2115
|
+
// starts at the root, so a click on /etc arrives as "//etc" — and
|
|
2116
|
+
// the path bar would then read "//etc", with every deeper click
|
|
2117
|
+
// stacking another slash. resolve() also collapses "..".
|
|
2118
|
+
const target = resolve(raw);
|
|
2091
2119
|
if (!isPathAllowed(target)) {
|
|
2092
|
-
sendJson(res, 400, { error:
|
|
2120
|
+
sendJson(res, 400, { error: NOT_READABLE + ": " + target });
|
|
2093
2121
|
return;
|
|
2094
2122
|
}
|
|
2095
2123
|
const listing = listDir(target, allFiles);
|
|
@@ -2117,7 +2145,7 @@ export function apply(ctx) {
|
|
|
2117
2145
|
return;
|
|
2118
2146
|
}
|
|
2119
2147
|
if (!isPathAllowed(path)) {
|
|
2120
|
-
sendJson(res, 400, { error:
|
|
2148
|
+
sendJson(res, 400, { error: NOT_READABLE + ": " + path });
|
|
2121
2149
|
return;
|
|
2122
2150
|
}
|
|
2123
2151
|
try {
|
|
@@ -2210,11 +2238,11 @@ export function apply(ctx) {
|
|
|
2210
2238
|
return;
|
|
2211
2239
|
}
|
|
2212
2240
|
if (!isPathAllowed(path)) {
|
|
2213
|
-
sendJson(res, 400, { error: "model
|
|
2241
|
+
sendJson(res, 400, { error: "model " + NOT_READABLE + ": " + path });
|
|
2214
2242
|
return;
|
|
2215
2243
|
}
|
|
2216
2244
|
if (mmproj && !isPathAllowed(mmproj)) {
|
|
2217
|
-
sendJson(res, 400, { error: "mmproj
|
|
2245
|
+
sendJson(res, 400, { error: "mmproj " + NOT_READABLE + ": " + mmproj });
|
|
2218
2246
|
return;
|
|
2219
2247
|
}
|
|
2220
2248
|
const result = await manager.run(path, Number.isFinite(ctx) ? ctx : 8192, Number.isFinite(mtp) ? mtp : 0, mmproj, effort, typeof body.mmprojCpu === "boolean" ? body.mmprojCpu : MMPROJ_CPU, moe, normalizePreserveThinking(body), kvTypeK, kvTypeV, normalizeSplitMode(body.splitMode), normalizeTensorSplit(body.tensorSplit));
|
|
@@ -2359,8 +2387,8 @@ export function apply(ctx) {
|
|
|
2359
2387
|
}
|
|
2360
2388
|
patch.binPath = wanted;
|
|
2361
2389
|
}
|
|
2362
|
-
// File-browser shortcuts
|
|
2363
|
-
//
|
|
2390
|
+
// File-browser shortcuts become one button each in the picker,
|
|
2391
|
+
// so a value here has to name a folder a person would: absolute,
|
|
2364
2392
|
// no "..", never the filesystem root, at most 12 entries.
|
|
2365
2393
|
// Missing dirs are still kept (an unmounted disk must not
|
|
2366
2394
|
// silently lose its shortcut); the tab marks them as missing.
|
package/package.json
CHANGED
|
@@ -37,9 +37,10 @@ cpuMoe, nCpuMoe, expertUsed, arch, updatedAt }`
|
|
|
37
37
|
|
|
38
38
|
- **Paths must exist and live on the fast mount**. Referencing the failing/
|
|
39
39
|
legacy `/mnt/disco1` is a red flag - targets are under `/mnt/raid0/GGUF/`.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
The tab can reach any absolute path (the picker's Up goes all the way to `/`,
|
|
41
|
+
so mounted disks are browsable without declaring them), so a path pointing
|
|
42
|
+
somewhere else is an operational mistake the guard will not catch - check it
|
|
43
|
+
by eye.
|
|
43
44
|
- **MTP depth must be <= 7** (the tab's ceiling; upstream clamps the
|
|
44
45
|
effective depth to the model's nextn depth) and **3 is the tuned value** —
|
|
45
46
|
deeper collapses at large ctx. Old profiles may still carry 4-6 from the
|
|
@@ -111,21 +112,21 @@ so a 131072 window with O = 32768 thresholds at **32768**, firing at
|
|
|
111
112
|
~32-38K pressure tokens and looping with xhigh + preserveThinking (every
|
|
112
113
|
turn re-adds 10-20K thinking tokens). A 98304 window is worse: pressure
|
|
113
114
|
exactly 0, proactive compaction disabled entirely. The fix is a per-model
|
|
114
|
-
headroom override
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
headroom override — but placement matters: the engines that run live
|
|
116
|
+
inside the agent-preset scopes (`preset-standard` → `plugins` →
|
|
117
|
+
`compaction` group → `compaction-basic`), NOT the top-level
|
|
118
|
+
`compaction-basic` row (disabled there; a patch on it is dead config that
|
|
119
|
+
dump-config will happily show). So the override must ride a wholesale
|
|
120
|
+
`config` on the preset row (row config is replaced whole, not merged):
|
|
121
|
+
copy the installed preset file
|
|
122
|
+
(`dsh-web-app/presets/standard.patch.yml` → `insert[0].config`), add
|
|
123
|
+
`modelPolicies` to the nested engine, paste as `- id: preset-standard`.
|
|
124
|
+
Compute values with `compactionBudgetFor(ctx).recommendedHeadroom`
|
|
125
|
+
(~6.5K for 131K → ~92K threshold; ~4K floor for 96K → ~61K). Because it
|
|
126
|
+
is wholesale, the row freezes: re-generate after any dsh upgrade that
|
|
127
|
+
touches the preset file, or upstream changes to that preset are silently
|
|
128
|
+
dropped. Only patch presets actually used (`standard` here); `ptc` /
|
|
129
|
+
`cordis` / `minimal` need the same treatment if adopted.
|
|
129
130
|
|
|
130
131
|
Shortcut with zero config edits: run the bigger window instead — the
|
|
131
132
|
200K/250K profiles threshold at ~106K/158K under defaults.
|