dsh-local-ai 0.2.10 → 0.2.11

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/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [0.2.11] - 2026-09-18
10
+
11
+ ### Fixed
12
+
13
+ - **A local route is never silently moved to the cloud by the image-offload circuit.** When the local stream failed with `IMAGE_OFFLOAD_REQUIRED` before producing content, the routing fallback treated it like any other failure and re-ran the request on the cloud — skipping the official offload circuit and leaking a local-only request to a remote provider. That code is now rethrown unchanged (both the finish-reason and the thrown-error paths), so the caller can offload the cited images and retry the same local route. Every other pre-content failure still falls back to the cloud.
14
+
15
+ ### Changed
16
+
17
+ - Migrate to the renamed attachment contract: `@deepseek-ai/dsh-attachment` replaced `ImageRequestPolicy` (`maxPixels`/`maxBytes`) with a per-route `ImageRequestTarget` (`width`/`height`/`maxBytes`). The adapter now requests a 2048×2048 target with a 10 MiB byte cap, which preserves the previous 4 Mi-pixel / 10 MiB projection exactly (a target at or above the source keeps the source dimensions).
18
+ - `/api/show` capability probes are cached for `visionCacheTtlMs` (default 30 s, `0` disables caching) instead of being issued once per request; `ollama_pull` and `ollama_remove` invalidate the cached entry for the affected model, and failures are cached for the window too so a down server is not probed per request.
19
+ - Align the dev/test dependency graph to `0.1.6-alpha.2`: `dsh-agent`, `dsh-user-approval`, `dsh-invariants` and `dsh-scope` were still resolving to `0.1.1-rc.2` as transitive peers, which broke the strict published-types pass (`CallId` no longer exists on `dsh-llm`). Declaring them explicitly removes the mixed-generation graph.
20
+ - Declare `dsh.manifestVersion: 1` and the canonical three-clause `engines.dsh`; refresh the `AGENTS.md`/CI baseline wording to the pinned line.
21
+
9
22
  ## [0.2.10] - 2026-09-12
10
23
 
11
24
  ### Changed
package/README-es.md CHANGED
@@ -8,6 +8,7 @@
8
8
  *Descubre, descarga, elimina e inspecciona modelos locales, enruta solicitudes hacia ellos por tipo de tarea o palabra clave con respaldo automático a la nube, y obtén un resumen de estado de un vistazo con `/ollama`.*
9
9
 
10
10
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
11
+ [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-local-ai)
11
12
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
12
13
  [![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-local-ai.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
13
14
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
@@ -26,7 +27,7 @@
26
27
 
27
28
  | Superficie | Estado |
28
29
  |---|---|
29
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2` (adaptado el 2026-09-09): el sobre de sesión conserva su campo ignorable solo para compatibilidad de lectura de logs almacenados - Session.append aún no puede estamparlo, por lo que el comportamiento de la puerta no cambia. Verificado el 2026-09-11 contra el checkout master dsh-v0.1.5-rc.2 (cadena de gates completa + smoke de instalación del profile). |
30
+ | Harness | DeepSeek Harness `dsh-v0.1.6-alpha.2` (verificado el 2026-09-18: doble typecheck + 133 pruebas + puertas self-contained/artifacts). El rango de peers admite todas las líneas soportadas: `>=0.1.2-rc.1 <0.2.0 \|\| >=0.1.5-alpha.1 <0.2.0 \|\| >=0.1.6-0 <0.2.0`; los pines dev/test son `0.1.6-alpha.2`. |
30
31
  | Node | `^22.19.0 \|\| >=24.0.0` |
31
32
  | Backend | [Ollama](https://ollama.com) (API HTTP local + sonda CLI) |
32
33
  | Modelo | Ruta solo texto (`inputModalities: ['text']`); se admiten llamadas y resultados de herramientas |
@@ -105,6 +106,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
105
106
  | `maxTokens` | `4096` | Límite de salida por solicitud cuando un modelo no tiene valor exacto |
106
107
  | `temperature` | *(none)* | Temperatura de muestreo por defecto (0..2); omitir deja el valor del proveedor |
107
108
  | `vision` | `true` | Declara y serializa el soporte de imágenes cuando el modelo informa vision; `false` mantiene la ruta solo texto |
109
+ | `visionCacheTtlMs` | `30000` | Milisegundos que se conserva en caché la sonda `/api/show` (`0` desactiva la caché; pull/remove invalida ese modelo) |
108
110
  | `models` | `[]` | Mapeos de nombre visible → modelo Ollama |
109
111
  | `models[].name` | *(required)* | Nombre de modelo visible en el harness (`GenerateOptions.model`) |
110
112
  | `models[].model` | `= name` | Id del modelo Ollama |
@@ -149,7 +151,7 @@ Todos los ajustes son campos `Config` de Schemastery (modificables desde cordis.
149
151
  - **Sin re-enrutamiento por defecto** — la lista `route` está vacía salvo que optes por lo contrario; una solicitud llega a un modelo local solo por una regla explícita o una selección explícita del proveedor `ollama`.
150
152
  - **Desinfección antes de mostrar** — las direcciones de endpoint y las rutas locales se desinfectan antes de llegar a la salida de herramientas, al comando `/ollama` o a los mensajes de error.
151
153
  - **Cero modelos empaquetados** — descargas y almacenamiento son responsabilidad de Ollama; nada se envía en el paquete.
152
- - **Fallo alto, fallo contenido** — la configuración inválida hace fallar el montaje; una ruta local que falla antes de producir contenido respalda a la nube (`next()`), de modo que un Ollama caído nunca bloquea una conversación.
154
+ - **Fallo alto, fallo contenido** — la configuración inválida hace fallar el montaje; una ruta local que falla antes de producir contenido respalda a la nube (`next()`), de modo que un Ollama caído nunca bloquea una conversación. **Única excepción:** un fallo con `IMAGE_OFFLOAD_REQUIRED` se relanza en vez de reintentarse en la nube: ese código es el circuito oficial de descarga de imágenes pidiendo a esta misma ruta local que suelte imágenes retenidas, y el respaldo saltaría el circuito enviando en silencio una solicitud solo-local a un proveedor remoto.
153
155
  - **Visible para el modelo ⟺ registrado** — el enrutamiento solo cambia qué proveedor sirve una solicitud (el mensaje del asistente se registra con su procedencia `ollama`); no se inventa entrada visible nueva.
154
156
 
155
157
  ## Known limitations
@@ -185,7 +187,7 @@ pnpm pack # el tarball publicado
185
187
 
186
188
  ## PerryLink DSH Plugin Family
187
189
 
188
- Este proyecto es uno de los [37 complementos de DeepSeek Harness](https://github.com/PerryLink) mantenidos por [PerryLink](https://github.com/PerryLink). Si este te ayuda, probablemente los demás también:
190
+ Este proyecto es uno de los [40 complementos de DeepSeek Harness](https://github.com/PerryLink) mantenidos por [PerryLink](https://github.com/PerryLink). Si este te ayuda, probablemente los demás también:
189
191
 
190
192
  | Plugin | One-liner |
191
193
  |---|---|
@@ -214,6 +216,7 @@ Este proyecto es uno de los [37 complementos de DeepSeek Harness](https://github
214
216
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Reglas de permisos declarativas allow/deny/ask estilo Claude Code con auditoría | |
215
217
  | **[dsh-personal-directive](https://github.com/PerryLink/dsh-personal-directive)** | Inyector de directivas personales con interruptor en la barra superior (edición framework) |
216
218
  | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | Base de conocimiento de desarrollo de plugins como habilidad de agente bajo demanda | |
219
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
217
220
  | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | Puente multicanal de aprobación/preguntas: WeChat/Telegram/Feishu, consola de sesión |
218
221
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | Motor de informes de investigación verificables con evidencia direccionada por contenido | |
219
222
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | Puntuación de calidad multidimensional para plugins de DeepSeek Harness. | |
@@ -224,7 +227,16 @@ Este proyecto es uno de los [37 complementos de DeepSeek Harness](https://github
224
227
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | Pruebas de instalación y humo aisladas para plugins de DeepSeek Harness. | |
225
228
  | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | Puente de tareas TickTick/Dida365: panel de cabecera de sesión + 11 herramientas |
226
229
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | Traducción de parámetros entre proveedores y reparación determinista de JSON para DeepSeek Harness. | |
227
- | **[dsh-wechat](https://github.com/PerryLink/dsh-wechat)** | Puente WeChat ↔ DSH (bot Tencent iLink): texto/imagen/archivo/voz, aprobaciones en el chat |
230
+ | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | Puente WeChat ↔ DSH (bot Tencent iLink): texto/imagen/archivo/voz, aprobaciones en el chat |
231
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
232
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
233
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
234
+ | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
235
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
236
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
237
+ | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
238
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
239
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
228
240
 
229
241
  ## License
230
242
 
package/README-hi.md CHANGED
@@ -8,6 +8,7 @@
8
8
  *स्थानीय मॉडल खोजें, खींचें, हटाएँ और जाँचें; कार्य-प्रकार या कीवर्ड के आधार पर अनुरोधों को स्थानीय मॉडल पर रूट करें और विफलता पर स्वतः क्लाउड पर वापस लौटें; `/ollama` से एक-झटके में स्थिति सारांश पाएँ।*
9
9
 
10
10
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
11
+ [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-local-ai)
11
12
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
12
13
  [![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-local-ai.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
13
14
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
@@ -26,7 +27,7 @@
26
27
 
27
28
  | सतह | स्थिति |
28
29
  |---|---|
29
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2` (2026-09-09 को अनुकूलित): सत्र लिफ़ाफ़ा अपना ignorable फ़ील्ड केवल संग्रहीत-लॉग पठन संगतता के लिए रखता है - Session.append अभी भी इसे स्टैम्प नहीं कर सकता, इसलिए गेट व्यवहार अपरिवर्तित है। 2026-09-11 को dsh-v0.1.5-rc.2 master checkout के विरुद्ध सत्यापित (पूर्ण गेट श्रृंखला + profile इंस्टॉल smoke)। |
30
+ | Harness | DeepSeek Harness `dsh-v0.1.6-alpha.2` (2026-09-18 को सत्यापित: दोहरा typecheck + 133 टेस्ट + self-contained/artifacts गेट)। peer रेंज सभी समर्थित लाइनों को स्वीकार करती है: `>=0.1.2-rc.1 <0.2.0 \|\| >=0.1.5-alpha.1 <0.2.0 \|\| >=0.1.6-0 <0.2.0`; dev/test पिन `0.1.6-alpha.2` हैं। |
30
31
  | Node | `^22.19.0 \|\| >=24.0.0` |
31
32
  | Backend | [Ollama](https://ollama.com) (स्थानीय HTTP API + CLI जाँच) |
32
33
  | Model | केवल-पाठ रूट (`inputModalities: ['text']`); टूल कॉल व परिणाम समर्थित हैं |
@@ -105,6 +106,7 @@ dsh --profile web --dump-config | grep -A2 'id: dsh-local-ai'
105
106
  | `maxTokens` | `4096` | जब मॉडल का कोई सटीक मान न हो तो प्रति-अनुरोध आउटपुट सीमा |
106
107
  | `temperature` | *(none)* | डिफ़ॉल्ट सैंपलिंग तापमान (0..2); छोड़ने पर प्रदाता डिफ़ॉल्ट रहता है |
107
108
  | `vision` | `true` | मॉडल द्वारा vision रिपोर्ट करने पर छवि समर्थन घोषित व सीरियलाइज़ करता है; `false` रूट को केवल-टेक्स्ट रखता है |
109
+ | `visionCacheTtlMs` | `30000` | `/api/show` क्षमता जाँच कितने मिलीसेकंड कैश रहे (`0` = कैश बंद; pull/remove उस मॉडल को अमान्य करता है) |
108
110
  | `models` | `[]` | Harness-दृश्य नाम → Ollama मॉडल मैपिंग |
109
111
  | `models[].name` | *(required)* | Harness-दृश्य मॉडल नाम (`GenerateOptions.model`) |
110
112
  | `models[].model` | `= name` | Ollama मॉडल id |
@@ -149,7 +151,7 @@ dsh --profile web --dump-config | grep -A2 'id: dsh-local-ai'
149
151
  - **डिफ़ॉल्ट रूप से कोई पुनः-रूटिंग नहीं** — `route` सूची तब तक खाली है जब तक आप सहमति न दें; अनुरोध स्थानीय मॉडल तक केवल किसी स्पष्ट नियम या `ollama` प्रदाता के स्पष्ट चयन से पहुँचता है।
150
152
  - **दिखाने से पहले सैनिटाइज़** — एंडपॉइंट पते व स्थानीय पथ टूल आउटपुट, `/ollama` कमांड या त्रुटि संदेशों तक पहुँचने से पहले सैनिटाइज़ होते हैं।
151
153
  - **शून्य बंडल मॉडल** — डाउनलोड व स्टोरेज Ollama की अपनी ज़िम्मेदारी है; पैकेज में कुछ नहीं भेजा जाता।
152
- - **विफलता ज़ोरदार, विफलता सीमित** — अमान्य कॉन्फ़िग माउंट को विफल करता है; सामग्री बनाने से पहले विफल स्थानीय रूट क्लाउड पर वापस लौटता है (`next()`), इसलिए बंद Ollama कभी बातचीत को अटकाता नहीं।
154
+ - **विफलता ज़ोरदार, विफलता सीमित** — अमान्य कॉन्फ़िग माउंट को विफल करता है; सामग्री बनाने से पहले विफल स्थानीय रूट क्लाउड पर वापस लौटता है (`next()`), इसलिए बंद Ollama कभी बातचीत को अटकाता नहीं। **एकमात्र अपवाद:** `IMAGE_OFFLOAD_REQUIRED` वाली विफलता क्लाउड पर दोबारा नहीं भेजी जाती बल्कि पुनः थ्रो होती है — यह कोड आधिकारिक इमेज-ऑफ़लोड सर्किट है जो इसी स्थानीय रूट से रखी गई छवियाँ हटाने को कहता है, और फ़ॉलबैक सर्किट को छोड़कर केवल-स्थानीय अनुरोध चुपचाप दूरस्थ प्रदाता को भेज देता।
153
155
  - **मॉडल-दृश्य ⟺ लॉग** — रूटिंग केवल यह बदलती है कि कौन-सा प्रदाता अनुरोध सेवा करता है (assistant संदेश अपनी `ollama` उत्पत्ति के साथ लॉग होता है); कोई नया मॉडल-दृश्य इनपुट नहीं गढ़ा जाता।
154
156
 
155
157
  ## Known limitations
@@ -185,7 +187,7 @@ pnpm pack # प्रकाशित tarball
185
187
 
186
188
  ## PerryLink DSH Plugin Family
187
189
 
188
- यह प्रोजेक्ट [PerryLink](https://github.com/PerryLink) द्वारा अनुरक्षित [37 DeepSeek Harness प्लगइनों](https://github.com/PerryLink) में से एक है। अगर यह आपकी मदद करता है, तो बाकी भी करेंगे:
190
+ यह प्रोजेक्ट [PerryLink](https://github.com/PerryLink) द्वारा अनुरक्षित [40 DeepSeek Harness प्लगइनों](https://github.com/PerryLink) में से एक है। अगर यह आपकी मदद करता है, तो बाकी भी करेंगे:
189
191
 
190
192
  | Plugin | One-liner |
191
193
  |---|---|
@@ -214,6 +216,7 @@ pnpm pack # प्रकाशित tarball
214
216
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | ऑडिट के साथ Claude Code-शैली घोषणात्मक allow/deny/ask अनुमति नियम | |
215
217
  | **[dsh-personal-directive](https://github.com/PerryLink/dsh-personal-directive)** | शीर्ष-बार टॉगल के साथ व्यक्तिगत निर्देश इंजेक्टर (फ्रेमवर्क संस्करण) |
216
218
  | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | माँग पर एजेंट कौशल के रूप में प्लगइन-विकास ज्ञान आधार | |
219
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
217
220
  | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | मल्टी-चैनल अनुमोदन/प्रश्न ब्रिज: WeChat/Telegram/Feishu, सत्र कंसोल |
218
221
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | सामग्री-पता साक्ष्य और सीलबंद संस्करणों वाला सत्यापन-योग्य अनुसंधान-रिपोर्ट इंजन | |
219
222
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | DeepSeek Harness प्लगिनों की बहु-आयामी गुणवत्ता स्कोरिंग। | |
@@ -224,7 +227,16 @@ pnpm pack # प्रकाशित tarball
224
227
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | DeepSeek Harness प्लगिनों के लिए पृथक इंस्टॉल-एंड-स्मोक टेस्ट ड्राइव। | |
225
228
  | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | TickTick/Dida365 कार्य ब्रिज: सत्र-हेडर पैनल + 11 टूल |
226
229
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | DeepSeek Harness के लिए वेंडर पैरामीटर अनुवाद और नियतात्मक JSON मरम्मत। | |
227
- | **[dsh-wechat](https://github.com/PerryLink/dsh-wechat)** | WeChat ↔ DSH ब्रिज (Tencent iLink bot): टेक्स्ट/इमेज/फ़ाइल/आवाज़, चैट में अनुमोदन |
230
+ | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | WeChat ↔ DSH ब्रिज (Tencent iLink bot): टेक्स्ट/इमेज/फ़ाइल/आवाज़, चैट में अनुमोदन |
231
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
232
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
233
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
234
+ | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
235
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
236
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
237
+ | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
238
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
239
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
228
240
 
229
241
  ## License
230
242
 
package/README-pt.md CHANGED
@@ -8,6 +8,7 @@
8
8
  *Descubra, baixe, remova e inspecione modelos locais, roteie solicitações para eles por tipo de tarefa ou palavra-chave com fallback automático para a nuvem e obtenha um resumo de status de relance com `/ollama`.*
9
9
 
10
10
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
11
+ [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-local-ai)
11
12
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
12
13
  [![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-local-ai.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
13
14
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
@@ -26,7 +27,7 @@
26
27
 
27
28
  | Superfície | Status |
28
29
  |---|---|
29
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2` (adaptado em 2026-09-09): o envelope de sessão mantém seu campo ignorable apenas para compatibilidade de leitura de logs armazenados - o Session.append ainda não consegue estampá-lo, então o comportamento da porta não muda. Verificado em 2026-09-11 contra o checkout master dsh-v0.1.5-rc.2 (cadeia de gates completa + smoke de instalação do profile). |
30
+ | Harness | DeepSeek Harness `dsh-v0.1.6-alpha.2` (verificado em 2026-09-18: typecheck duplo + 133 testes + portas self-contained/artifacts). O intervalo de peers admite todas as linhas suportadas: `>=0.1.2-rc.1 <0.2.0 \|\| >=0.1.5-alpha.1 <0.2.0 \|\| >=0.1.6-0 <0.2.0`; os pins dev/test são `0.1.6-alpha.2`. |
30
31
  | Node | `^22.19.0 \|\| >=24.0.0` |
31
32
  | Backend | [Ollama](https://ollama.com) (API HTTP local + sonda CLI) |
32
33
  | Modelo | Rota somente texto (`inputModalities: ['text']`); chamadas e resultados de ferramentas são suportados |
@@ -105,6 +106,7 @@ Todos os ajustes são campos `Config` de Schemastery (modificáveis pelo cordis.
105
106
  | `maxTokens` | `4096` | Limite de saída por solicitação quando um modelo não tem valor exato |
106
107
  | `temperature` | *(none)* | Temperatura de amostragem padrão (0..2); omitir mantém o padrão do provedor |
107
108
  | `vision` | `true` | Declara e serializa o suporte a imagens quando o modelo informa vision; `false` mantém a rota somente texto |
109
+ | `visionCacheTtlMs` | `30000` | Milissegundos em que a sondagem `/api/show` fica em cache (`0` desativa; pull/remove invalida esse modelo) |
108
110
  | `models` | `[]` | Mapeamentos nome visível → modelo Ollama |
109
111
  | `models[].name` | *(required)* | Nome de modelo visível no harness (`GenerateOptions.model`) |
110
112
  | `models[].model` | `= name` | Id do modelo Ollama |
@@ -149,7 +151,7 @@ Todos os ajustes são campos `Config` de Schemastery (modificáveis pelo cordis.
149
151
  - **Sem re-roteamento por padrão** — a lista `route` fica vazia salvo se você optar; uma solicitação chega a um modelo local apenas por uma regra explícita ou seleção explícita do provedor `ollama`.
150
152
  - **Sanitização antes de exibir** — endereços de endpoint e caminhos locais são sanitizados antes de chegar à saída de ferramentas, ao comando `/ollama` ou a mensagens de erro.
151
153
  - **Zero modelos empacotados** — downloads e armazenamento são responsabilidade do Ollama; nada é enviado no pacote.
152
- - **Falha alta, falha contida** — configuração inválida faz o montagem falhar; uma rota local que falha antes de produzir conteúdo faz fallback para a nuvem (`next()`), de modo que um Ollama caído nunca trava uma conversa.
154
+ - **Falha alta, falha contida** — configuração inválida faz o montagem falhar; uma rota local que falha antes de produzir conteúdo faz fallback para a nuvem (`next()`), de modo que um Ollama caído nunca trava uma conversa. **Única exceção:** uma falha com `IMAGE_OFFLOAD_REQUIRED` é relançada em vez de repetida na nuvem — esse código é o circuito oficial de descarga de imagens pedindo a esta mesma rota local que libere imagens retidas, e o fallback pularia o circuito enviando em silêncio uma solicitação somente-local a um provedor remoto.
153
155
  - **Visível ao modelo ⟺ registrado** — o roteamento só muda qual provedor atende uma solicitação (a mensagem do assistente é registrada com sua proveniência `ollama`); nenhuma entrada visível nova é inventada.
154
156
 
155
157
  ## Known limitations
@@ -185,7 +187,7 @@ pnpm pack # o tarball publicado
185
187
 
186
188
  ## PerryLink DSH Plugin Family
187
189
 
188
- Este projeto é um dos [37 plugins de DeepSeek Harness](https://github.com/PerryLink) mantidos por [PerryLink](https://github.com/PerryLink). Se este ajuda você, os outros provavelmente também:
190
+ Este projeto é um dos [40 plugins de DeepSeek Harness](https://github.com/PerryLink) mantidos por [PerryLink](https://github.com/PerryLink). Se este ajuda você, os outros provavelmente também:
189
191
 
190
192
  | Plugin | One-liner |
191
193
  |---|---|
@@ -214,6 +216,7 @@ Este projeto é um dos [37 plugins de DeepSeek Harness](https://github.com/Perry
214
216
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Regras de permissão declarativas allow/deny/ask estilo Claude Code com auditoria | |
215
217
  | **[dsh-personal-directive](https://github.com/PerryLink/dsh-personal-directive)** | Injetor de diretivas pessoais com alternância na barra superior (edição framework) |
216
218
  | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | Base de conhecimento de desenvolvimento de plugins como habilidade de agente sob demanda | |
219
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
217
220
  | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | Ponte multicanal de aprovação/perguntas: WeChat/Telegram/Feishu, console de sessão |
218
221
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | Motor de relatórios de pesquisa verificáveis com evidência endereçada por conteúdo | |
219
222
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | Pontuação de qualidade multidimensional para plugins de DeepSeek Harness. | |
@@ -224,7 +227,16 @@ Este projeto é um dos [37 plugins de DeepSeek Harness](https://github.com/Perry
224
227
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | Test drives isolados de instalação e smoke para plugins de DeepSeek Harness. | |
225
228
  | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | Ponte de tarefas TickTick/Dida365: painel no cabeçalho da sessão + 11 ferramentas |
226
229
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | Tradução de parâmetros entre fornecedores e reparo determinístico de JSON para DeepSeek Harness. | |
227
- | **[dsh-wechat](https://github.com/PerryLink/dsh-wechat)** | Ponte WeChat ↔ DSH (bot Tencent iLink): texto/imagem/arquivo/voz, aprovações no chat |
230
+ | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | Ponte WeChat ↔ DSH (bot Tencent iLink): texto/imagem/arquivo/voz, aprovações no chat |
231
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
232
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
233
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
234
+ | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
235
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
236
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
237
+ | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
238
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
239
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
228
240
 
229
241
  ## License
230
242
 
package/README-zh.md CHANGED
@@ -10,6 +10,7 @@
10
10
  > **官方仓库。** 本仓库是 dsh-local-ai 的唯一官方仓库,由 PerryLink 维护。其他账号下的同名仓库与本项目无关。
11
11
 
12
12
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
13
+ [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-local-ai)
13
14
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
14
15
  [![dsh-doctor](https://raw.githubusercontent.com/PerryLink/dsh-plugin-doctor/main/badges/PerryLink__dsh-local-ai.svg)](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章)
15
16
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
@@ -28,7 +29,7 @@
28
29
 
29
30
  | 项目 | 状态 |
30
31
  |---|---|
31
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2`(2026-09-09 已适配):会话信封保留 ignorable 字段但仅用于存量日志读取兼容——Session.append 仍无法盖章,门控行为不变。已于 2026-09-11 对照 dsh-v0.1.5-rc.2 master checkout 核验(完整门控链 + profile 安装冒烟)。 |
32
+ | Harness | DeepSeek Harness `dsh-v0.1.6-alpha.2`(2026-09-18 核验:双 typecheck 尺子 + 133 项测试 + self-contained/artifacts 门)。peer 范围接纳全部受支持线:`>=0.1.2-rc.1 <0.2.0 \|\| >=0.1.5-alpha.1 <0.2.0 \|\| >=0.1.6-0 <0.2.0`;dev/test 钉号 `0.1.6-alpha.2`。 |
32
33
  | Node | `^22.19.0 \|\| >=24.0.0` |
33
34
  | 后端 | [Ollama](https://ollama.com)(本地 HTTP API + CLI 探测) |
34
35
  | 模型 | 纯文本路由(`inputModalities: ['text']`);支持工具调用与工具结果 |
@@ -107,6 +108,7 @@ dsh --profile web --dump-config | grep -A2 'id: dsh-local-ai'
107
108
  | `maxTokens` | `4096` | 模型无精确值时的单请求输出上限 |
108
109
  | `temperature` | *(none)* | 默认采样温度(0..2);省略则用提供方默认值 |
109
110
  | `vision` | `true` | 模型报告 vision 能力时声明并序列化图片支持;`false` 保持纯文本路由 |
111
+ | `visionCacheTtlMs` | `30000` | `/api/show` 能力探测的缓存毫秒数(`0` 关闭缓存;pull/remove 会使该模型失效) |
110
112
  | `models` | `[]` | Harness 可见名 → Ollama 模型映射 |
111
113
  | `models[].name` | *(required)* | Harness 可见模型名(`GenerateOptions.model`) |
112
114
  | `models[].model` | `= name` | Ollama 模型 id |
@@ -151,7 +153,7 @@ dsh --profile web --dump-config | grep -A2 'id: dsh-local-ai'
151
153
  - **默认不重路由** —— `route` 列表默认为空;请求只有通过显式规则或显式选择 `ollama` 提供方才会到达本地模型。
152
154
  - **展示前脱敏** —— 端点地址与本地路径在进入工具输出、`/ollama` 命令或错误消息前都会被脱敏。
153
155
  - **零捆绑模型** —— 下载与存储是 Ollama 自己的事;包内不含任何模型。
154
- - **失败响亮、失败可控** —— 非法配置导致挂载失败;本地路由在产出内容前失败会回退云端(`next()`),因此 Ollama 宕机不会卡死对话。
156
+ - **失败响亮、失败可控** —— 非法配置导致挂载失败;本地路由在产出内容前失败会回退云端(`next()`),因此 Ollama 宕机不会卡死对话。**唯一例外**:带 `IMAGE_OFFLOAD_REQUIRED` 的失败会被重新抛出而不回退云端——该码是官方图像卸载回路在要求这条本地路由卸下保留图片,回退会跳过回路并把本应只在本地的请求静默发往远端提供方。
155
157
  - **模型可见 ⟺ 已记录** —— 路由只改变由哪个提供方服务请求(assistant 消息会以 `ollama` 来源记录);不凭空新增模型可见输入。
156
158
 
157
159
  ## Known limitations
@@ -187,7 +189,7 @@ pnpm pack # 发布用 tarball
187
189
 
188
190
  ## PerryLink DSH Plugin Family
189
191
 
190
- 这是 [PerryLink](https://github.com/PerryLink) 维护的 [37 个 DeepSeek Harness 插件](https://github.com/PerryLink) 之一。如果它能帮到你,其他的也会:
192
+ 这是 [PerryLink](https://github.com/PerryLink) 维护的 [40 个 DeepSeek Harness 插件](https://github.com/PerryLink) 之一。如果它能帮到你,其他的也会:
191
193
 
192
194
  | Plugin | One-liner |
193
195
  |---|---|
@@ -216,6 +218,7 @@ pnpm pack # 发布用 tarball
216
218
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Claude Code 风格声明式 allow/deny/ask 权限规则,带审计 | |
217
219
  | **[dsh-personal-directive](https://github.com/PerryLink/dsh-personal-directive)** | 个人指令注入器:顶栏开关(框架版) |
218
220
  | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | 作为按需代理技能的插件开发知识库 | |
221
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
219
222
  | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | 多渠道审批/提问桥接:微信/Telegram/飞书,会话控制台 |
220
223
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | 可验证研究报告引擎:内容寻址证据账本与封存版本 | |
221
224
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | DeepSeek Harness 插件的多维质量评分。 | |
@@ -226,7 +229,16 @@ pnpm pack # 发布用 tarball
226
229
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | DeepSeek Harness 插件的隔离试装冒烟。 | |
227
230
  | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | TickTick/滴答清单任务桥接:会话头面板 + 11 个工具 |
228
231
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | DeepSeek Harness 的厂商参数翻译与确定性 JSON 修复。 | |
229
- | **[dsh-wechat](https://github.com/PerryLink/dsh-wechat)** | 微信 ↔ DSH 桥接(Tencent iLink 机器人):文本/图片/文件/语音,聊天内审批卡片 |
232
+ | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | 微信 ↔ DSH 桥接(Tencent iLink 机器人):文本/图片/文件/语音,聊天内审批卡片 |
233
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
234
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
235
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
236
+ | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
237
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
238
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
239
+ | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
240
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
241
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
230
242
 
231
243
  ## License
232
244
 
package/README.md CHANGED
@@ -29,7 +29,7 @@
29
29
 
30
30
  | Surface | Status |
31
31
  |---|---|
32
- | Harness | DeepSeek Harness `dsh-v0.1.5-rc.2` (adapted 2026-09-09): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. Verified 2026-09-11 against the dsh-v0.1.5-rc.2 master checkout (full gate chain + profile install smoke). |
32
+ | Harness | DeepSeek Harness `dsh-v0.1.6-alpha.2` (verified 2026-09-18: dual typecheck rulers + 133 tests + self-contained/artifacts gates). The peer range admits every supported line: `>=0.1.2-rc.1 <0.2.0 \|\| >=0.1.5-alpha.1 <0.2.0 \|\| >=0.1.6-0 <0.2.0`; dev/test pins are `0.1.6-alpha.2`. |
33
33
  | Node | `^22.19.0 \|\| >=24.0.0` |
34
34
  | Backend | [Ollama](https://ollama.com) (local HTTP API + CLI probe) |
35
35
  | Model | Text-only route (`inputModalities: ['text']`); tool calls and tool results are supported |
@@ -110,6 +110,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
110
110
  | `maxTokens` | `4096` | Per-request output cap used when a model has no exact value |
111
111
  | `temperature` | *(none)* | Default sampling temperature (0..2); omitted leaves the provider default |
112
112
  | `vision` | `true` | Declare and serialize image support when the model reports vision; `false` keeps the route text-only |
113
+ | `visionCacheTtlMs` | `30000` | Milliseconds a `/api/show` capability probe stays cached (`0` disables caching; a pull or remove invalidates that model) |
113
114
  | `models` | `[]` | Harness-visible → Ollama model mappings |
114
115
  | `models[].name` | *(required)* | Harness-visible model name (`GenerateOptions.model`) |
115
116
  | `models[].model` | `= name` | Ollama model id |
@@ -154,7 +155,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
154
155
  - **No re-routing by default** — the `route` list is empty unless you opt in; a request reaches a local model only through an explicit rule or an explicit `ollama` provider selection.
155
156
  - **Sanitize before display** — endpoint addresses and local paths are sanitized before they reach tool output, the `/ollama` command, or error messages.
156
157
  - **Zero bundled models** — downloads and storage are Ollama's own responsibility; nothing is shipped in the package.
157
- - **Failure loud, failure contained** — invalid config fails the mount; a local route that fails before producing content falls back to the cloud (`next()`), so a down Ollama never bricks a conversation.
158
+ - **Failure loud, failure contained** — invalid config fails the mount; a local route that fails before producing content falls back to the cloud (`next()`), so a down Ollama never bricks a conversation. **One carve-out:** a failure carrying `IMAGE_OFFLOAD_REQUIRED` is rethrown instead of retried on the cloud — that code is the official offload circuit asking this same local route to drop retained images, and falling back would skip the circuit while silently sending a local-only request to a remote provider.
158
159
  - **Model-visible ⟺ logged** — routing only changes which provider serves a request (the assistant message is logged with its `ollama` provenance); no new model-visible input is invented.
159
160
 
160
161
  ## Known limitations
@@ -190,7 +191,7 @@ pnpm pack # the published tarball
190
191
 
191
192
  ## PerryLink DSH Plugin Family
192
193
 
193
- This project is one of the [37 DeepSeek Harness plugins](https://github.com/PerryLink) maintained by [PerryLink](https://github.com/PerryLink). If this one helps you, the others likely will too:
194
+ This project is one of the [40 DeepSeek Harness plugins](https://github.com/PerryLink) maintained by [PerryLink](https://github.com/PerryLink). If this one helps you, the others likely will too:
194
195
 
195
196
  | Plugin | One-liner |
196
197
  |---|---|
@@ -219,6 +220,7 @@ This project is one of the [37 DeepSeek Harness plugins](https://github.com/Perr
219
220
  | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Claude Code-style declarative allow/deny/ask permission rules with audit | |
220
221
  | **[dsh-personal-directive](https://github.com/PerryLink/dsh-personal-directive)** | Personal directive injector with top-bar toggle (framework edition) |
221
222
  | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | Plugin-development knowledge base as an on-demand agent skill | |
223
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | Zero-dependency static + sandbox smoke detector for DSH plugins | |
222
224
  | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | Multi-channel approval/question bridge: WeChat/Telegram/Feishu, session console |
223
225
  | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | Verifiable research-report engine: content-addressed evidence ledger and sealed versions | |
224
226
  | **[dsh-score](https://github.com/PerryLink/dsh-score)** | Multi-dimensional quality scoring for DeepSeek Harness plugins. | |
@@ -229,7 +231,16 @@ This project is one of the [37 DeepSeek Harness plugins](https://github.com/Perr
229
231
  | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | Isolated install-and-smoke test drives for DeepSeek Harness plugins. | |
230
232
  | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | TickTick/Dida365 task bridge: session-header panel + 11 tools |
231
233
  | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | Vendor parameter translation and deterministic JSON repair for DeepSeek Harness. | |
232
- | **[dsh-wechat](https://github.com/PerryLink/dsh-wechat)** | WeChat ↔ DSH bridge (Tencent iLink bot): text/image/file/voice, approvals in chat |
234
+ | **[dsh-wechat](https://github.com/pan17/dsh-wechat)** | WeChat ↔ DSH bridge (Tencent iLink bot): text/image/file/voice, approvals in chat |
235
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | Automatic strong/cheap model-tier routing with deterministic risk guards and a `/tier` command | |
236
+ | **[dsh-catalog](https://github.com/PerryLink/dsh-catalog)** | DSH Desktop Market standard catalog source for the PerryLink family | |
237
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | Read-only MCP server exposing the certification registry: grades, snapshots and five-dimension evidence | |
238
+ | **[dsh-kit](https://github.com/PerryLink/dsh-kit)** | One-command starter pack that installs the core family | |
239
+ | **[dsh-plugin-certification](https://github.com/PerryLink/dsh-plugin-certification)** | Community certification registry with repro-checkable grades and badges | |
240
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | Shared zero-runtime-dependency toolkit for the PerryLink DSH plugins | |
241
+ | **[dsh-plugin-portal](https://github.com/PerryLink/dsh-plugin-portal)** | Zero-dependency static portal rendering the whole plugin family as one page | |
242
+ | **[dsh-plugin-upgrade-015](https://github.com/PerryLink/dsh-plugin-upgrade-015)** | Merged `0.1.3-alpha.1` → `0.1.5-rc.1` upgrade corridor card plus a zero-dependency seam scanner | |
243
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | Cross-session team rooms: shared message bus, task board and timeline | |
233
244
 
234
245
  ### Install from the DSH Desktop Market
235
246
 
package/cordis.patch.yml CHANGED
@@ -23,6 +23,9 @@
23
23
  # temperature: 0.7
24
24
  # Declare image support when the model reports vision; false keeps text-only.
25
25
  # vision: true
26
+ # Milliseconds a /api/show capability probe stays cached (0 disables
27
+ # caching; pull/remove invalidate the entry for that model).
28
+ # visionCacheTtlMs: 30000
26
29
  # Harness-visible -> Ollama model mappings. Each entry maps a name you
27
30
  # select in the harness to an Ollama model id (defaults to the name).
28
31
  models: []
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineTool } from "@deepseek-ai/dsh-tools";
2
2
  import z from "@deepseek-ai/schemastery";
3
- import { EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, attributionHeaders, contentHasImage } from "@deepseek-ai/dsh-llm";
3
+ import { EMPTY_RESPONSE_CODE, IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, attributionHeaders, contentHasImage } from "@deepseek-ai/dsh-llm";
4
4
  import { idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
5
5
  import { tmpdir } from "node:os";
6
6
  //#region src/config.ts
@@ -24,6 +24,7 @@ const Config = z.object({
24
24
  maxTokens: z.number().default(4096),
25
25
  temperature: z.number(),
26
26
  vision: z.boolean().default(true),
27
+ visionCacheTtlMs: z.number().default(3e4),
27
28
  models: z.array(z.object({
28
29
  name: z.string().required(),
29
30
  model: z.string(),
@@ -57,6 +58,10 @@ const Config = z.object({
57
58
  function assertPositiveInt(name, value) {
58
59
  if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError(`${name} must be a positive safe integer, got ${String(value)}`);
59
60
  }
61
+ /** Throw unless `value` is a non-negative safe integer (`0` disables a cache). */
62
+ function assertNonNegativeInt(name, value) {
63
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${name} must be a non-negative safe integer, got ${String(value)}`);
64
+ }
60
65
  /** Throw unless `value` is a finite number in `[min, max]`. */
61
66
  function assertFiniteRange(name, value, min, max) {
62
67
  if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) throw new TypeError(`${name} must be a finite number in [${min}, ${max}], got ${String(value)}`);
@@ -101,6 +106,8 @@ function resolveConfig(config = {}) {
101
106
  const temperature = config.temperature;
102
107
  if (temperature !== void 0) assertFiniteRange("temperature", temperature, 0, 2);
103
108
  const vision = config.vision ?? true;
109
+ const visionCacheTtlMs = config.visionCacheTtlMs ?? 3e4;
110
+ assertNonNegativeInt("visionCacheTtlMs", visionCacheTtlMs);
104
111
  const models = resolveMappings(config.models, "models");
105
112
  const backends = (config.backends ?? []).map((backend, index) => {
106
113
  if (typeof backend.name !== "string" || backend.name.trim().length === 0) throw new TypeError(`backends[${index}].name must be a non-empty string`);
@@ -149,6 +156,7 @@ function resolveConfig(config = {}) {
149
156
  maxTokens,
150
157
  ...temperature === void 0 ? {} : { temperature },
151
158
  vision,
159
+ visionCacheTtlMs,
152
160
  models,
153
161
  backends,
154
162
  route
@@ -956,12 +964,18 @@ function _usingCtx() {
956
964
  /** Idle-timeout abort code stamped onto a stalled stream's timeout reason. */
957
965
  const STREAM_IDLE_TIMEOUT_CODE$1 = "LLM_STREAM_IDLE_TIMEOUT";
958
966
  /**
959
- * Deterministic request-image policy: aspect-preserving projection at ≤ 4 MP
960
- * and a 10 MiB encoded-byte cap per image (a protocol default, not a tunable —
961
- * the attachment service owns admission limits).
967
+ * Deterministic request-image target for this adapter's image projection. The
968
+ * 0.1.6 attachment service replaced the per-request policy object
969
+ * (`ImageRequestPolicy`: `maxPixels`/`maxBytes`) with a per-route target
970
+ * (`ImageRequestTarget`: `width`/`height`/`maxBytes`), where a target at or
971
+ * above the source keeps the source dimensions. 2048×2048 therefore preserves
972
+ * the previous 4 Mi-pixel ceiling exactly, and `maxBytes` keeps the 10 MiB
973
+ * encoded-byte cap per image (a protocol default, not a tunable — the
974
+ * attachment service owns admission limits).
962
975
  */
963
- const REQUEST_IMAGE_POLICY = {
964
- maxPixels: 4194304,
976
+ const REQUEST_IMAGE_TARGET = {
977
+ width: 2048,
978
+ height: 2048,
965
979
  maxBytes: 10485760
966
980
  };
967
981
  /** The single provider route this adapter owns. */
@@ -986,6 +1000,12 @@ function modelInfo(provider, id, name, vision) {
986
1000
  */
987
1001
  var OllamaAdapter = class extends LlmAdapter {
988
1002
  options;
1003
+ /**
1004
+ * `/api/show` capability cache keyed by `baseURL|model`, so a conversation
1005
+ * that resolves the same model repeatedly probes once per TTL window instead
1006
+ * of once per request. Pull/remove invalidate explicitly.
1007
+ */
1008
+ visionCache = /* @__PURE__ */ new Map();
989
1009
  constructor(options) {
990
1010
  super();
991
1011
  this.options = options;
@@ -993,14 +1013,38 @@ var OllamaAdapter = class extends LlmAdapter {
993
1013
  fetchImpl() {
994
1014
  return this.options.fetchImpl ?? ((input, init) => globalThis.fetch(input, init));
995
1015
  }
1016
+ /**
1017
+ * Drop cached capability probes. Called after a model is pulled or removed,
1018
+ * where the cached answer is known to be stale.
1019
+ * @param name - the Ollama model id, or undefined to clear every entry.
1020
+ */
1021
+ invalidateVision(name) {
1022
+ if (name === void 0) {
1023
+ this.visionCache.clear();
1024
+ return;
1025
+ }
1026
+ for (const key of [...this.visionCache.keys()]) if (key.endsWith(`|${name}`)) this.visionCache.delete(key);
1027
+ }
996
1028
  /** Probe one model's `/api/show` capabilities; failures degrade to text-only. */
997
1029
  async visionOf(resolved, name, signal) {
998
1030
  if (!resolved.vision) return false;
1031
+ const key = `${resolved.baseURL}|${name}`;
1032
+ const ttl = resolved.visionCacheTtlMs;
1033
+ if (ttl > 0) {
1034
+ const cached = this.visionCache.get(key);
1035
+ if (cached !== void 0 && Date.now() - cached.at < ttl) return cached.vision;
1036
+ }
1037
+ let vision = false;
999
1038
  try {
1000
- return hasVision((await showModel(resolved.baseURL, name, this.fetchImpl(), signal)).capabilities);
1039
+ vision = hasVision((await showModel(resolved.baseURL, name, this.fetchImpl(), signal)).capabilities);
1001
1040
  } catch {
1002
- return false;
1041
+ vision = false;
1003
1042
  }
1043
+ if (ttl > 0) this.visionCache.set(key, {
1044
+ vision,
1045
+ at: Date.now()
1046
+ });
1047
+ return vision;
1004
1048
  }
1005
1049
  providerInfo(provider) {
1006
1050
  return {
@@ -1096,7 +1140,7 @@ var OllamaAdapter = class extends LlmAdapter {
1096
1140
  for (const message of options.messages) for (const block of message.content) {
1097
1141
  if (block.type !== "image") continue;
1098
1142
  const ref = block.attachment;
1099
- const requestImage = await attachments.readImageRequest(ref, REQUEST_IMAGE_POLICY, signal);
1143
+ const requestImage = await attachments.readImageRequest(ref, REQUEST_IMAGE_TARGET, signal);
1100
1144
  map.set(String(ref.attachmentId), Buffer.from(requestImage.data).toString("base64"));
1101
1145
  }
1102
1146
  return map;
@@ -1606,6 +1650,16 @@ var OpenAICompatibleAdapter = class extends LlmAdapter {
1606
1650
  //#endregion
1607
1651
  //#region src/route.ts
1608
1652
  /**
1653
+ * Whether a local-route failure is the official image-offload circuit asking
1654
+ * THIS route to drop retained images and retry: the request fits nowhere until
1655
+ * the named occurrences are offloaded.
1656
+ * @param failure - the finish reason's failure facts, when present.
1657
+ * @returns true for `IMAGE_OFFLOAD_REQUIRED`.
1658
+ */
1659
+ function isImageOffloadRequired(failure) {
1660
+ return failure?.code === IMAGE_OFFLOAD_REQUIRED_CODE;
1661
+ }
1662
+ /**
1609
1663
  * Whether a stream chunk carries visible model output for fallback timing.
1610
1664
  * The host moved this helper out of `@deepseek-ai/dsh-llm` on 0.1.2-alpha.2
1611
1665
  * (it now lives in the client ui-chat package), so the plugin pins the same
@@ -1680,6 +1734,11 @@ function decideRoute(options, resolved) {
1680
1734
  * local route keeps retry and failure normalization). If the local route
1681
1735
  * finishes with an error or aborts before any token delta, `next()` (the
1682
1736
  * cloud) is streamed instead; otherwise the local stream is forwarded.
1737
+ *
1738
+ * One failure is deliberately NOT retried on the cloud: `IMAGE_OFFLOAD_REQUIRED`
1739
+ * is the official image-offload circuit asking this same route to drop retained
1740
+ * images. Falling back there would skip the circuit and silently move a
1741
+ * local-only request to a remote provider, so the signal is rethrown instead.
1683
1742
  * @param streamLocal - produces the local stream for a re-routed request.
1684
1743
  * @param options - the original request.
1685
1744
  * @param decision - the local model to route to.
@@ -1702,6 +1761,7 @@ async function* routeLocal(streamLocal, options, decision, next) {
1702
1761
  }
1703
1762
  if (chunk.type === "finish") {
1704
1763
  if (chunk.reason.kind === "error" || chunk.reason.kind === "aborted") {
1764
+ if (isImageOffloadRequired(chunk.reason.failure)) throw new LlmError(chunk.reason.failure.message, chunk.reason.failure.code);
1705
1765
  yield* next();
1706
1766
  return;
1707
1767
  }
@@ -1717,12 +1777,13 @@ async function* routeLocal(streamLocal, options, decision, next) {
1717
1777
  }
1718
1778
  for (const buffered of pending) yield buffered;
1719
1779
  } catch (error) {
1720
- if (!producedContent) {
1721
- yield* next();
1722
- return;
1780
+ if (producedContent) {
1781
+ for (const buffered of pending) yield buffered;
1782
+ throw error;
1723
1783
  }
1724
- for (const buffered of pending) yield buffered;
1725
- throw error;
1784
+ if (isImageOffloadRequired(error)) throw error;
1785
+ yield* next();
1786
+ return;
1726
1787
  }
1727
1788
  }
1728
1789
  //#endregion
@@ -1818,7 +1879,7 @@ async function checkHealth(baseURL, fetchImpl, subprocess, requestTimeoutMs, gra
1818
1879
  * The `dsh-local-ai` plugin version. The release script bumps this string
1819
1880
  * alongside `package.json`; `test/version.spec.ts` trips when the two drift.
1820
1881
  */
1821
- const VERSION = "0.2.10";
1882
+ const VERSION = "0.2.11";
1822
1883
  //#endregion
1823
1884
  //#region src/index.ts
1824
1885
  const name = "local-ai";
@@ -1983,9 +2044,11 @@ function apply(ctx, config = {}) {
1983
2044
  },
1984
2045
  async execute(args, exec) {
1985
2046
  const name = args.name;
2047
+ const result = await pullModel(resolved.baseURL, name, fetchImpl, exec.signal);
2048
+ adapter.invalidateVision(name);
1986
2049
  return {
1987
2050
  name,
1988
- status: (await pullModel(resolved.baseURL, name, fetchImpl, exec.signal)).status
2051
+ status: result.status
1989
2052
  };
1990
2053
  }
1991
2054
  }));
@@ -2007,6 +2070,7 @@ function apply(ctx, config = {}) {
2007
2070
  async execute(args, exec) {
2008
2071
  const name = args.name;
2009
2072
  await removeModel(resolved.baseURL, name, fetchImpl, exec.signal);
2073
+ adapter.invalidateVision(name);
2010
2074
  return {
2011
2075
  name,
2012
2076
  removed: true