{"ok":true,"value":{"id":"vision-control","name":"vision-control","version":"0.1.4","developer":"DeepSeek Harness","description":"管理 llm-pi-ai 按模型的图片视觉开关；只写自有来源标记的配置，不覆盖用户声明。","sha256":"097cb0b4c5e448440278f0a3147a130f67fdc409b1bdedd3078dab27247cef8a","byteSize":23861,"publishedAt":"2026-08-25T10:53:52.446Z","updatedAt":"2026-09-21T17:16:41.042Z","releasedAt":"2026-09-21T17:16:41.042Z","packageName":"vision-control","loaderId":"vision-control","changelog":"","readme":{"md":"# vision-control\n\nOut-of-tree Cordis plugin that manages the `llm-pi-ai` image-vision switch per\nmodel (no main-program changes, no UI — a pure configuration plane).\n\nVersion: 0.1.3\n\n## Purpose\n\nWrites to the `llm-pi-ai` user section through `dsh-settings`, doing two things:\n\n- **Vision on by default**: when a route declares no `defaultInput` in either\n  the user section or the composition `base` layer, write route-level\n  `defaultInput: [text, image]`. A declared `defaultInput` (even `[text]`) is\n  never overwritten.\n- **Vision off per model**: for models listed in `disabled`, write `input:\n  [text]` to switch vision off; when a model leaves `disabled`, undo only the\n  entry this plugin wrote itself.\n\n## Ownership and undo rules (since 0.1.1)\n\nThe plugin writes only fields it \"owns\", and records every write with a\ndeterministic provenance marker:\n\n- Disabling a model on a **hand-declared route** (a route whose config carries a\n  `models` list) writes `input: [text]` only when the model entry declares **no\n  `input`**, together with the provenance marker\n  `_visionControl: { input: true }`.\n- Disabling a model on a **catalog route** (no `models` list; served from the\n  installed pi-ai catalog) writes\n  `modelOverrides.<id>: { input: [text], _visionControl: { input: true } }`\n  only when the authoritative host model catalog does describe that model on\n  that route.\n- Writing route-level `defaultInput: [text, image]` also records the marker\n  `_visionControl: { defaultInput: true }` on the route object.\n- When a disable is lifted or `defaultImage` is set to `false`, **only marker\n  verified writes are removed**. Any unmarked `input` / `defaultInput` —\n  including a user-authored `input: [text]` — is treated as user-owned and is\n  never automatically deleted or rewritten.\n- If the user later edits a value under a marker (an `input` or a route\n  `defaultInput`), the plugin releases ownership of that entry (removes the\n  marker, keeps the user's value) and will not auto-undo it.\n- A route that exists only in the resolved layer (never user-declared) is never\n  materialized into the user document.\n- Idempotent: repeated loads and repeated reconciles have no side effects;\n  config validation failures error at load, never silently skipped.\n\n### Catalog-route disable verification\n\nBefore disabling a model on a catalog route, the plugin verifies membership\nthrough the runtime `llm` service (`ctx.get('llm')`): the adapter that service\nmaterializes for that route is exactly the set of models for which `llm-pi-ai`\nwould accept a `modelOverrides` entry. When membership **cannot be verified**\n(no `llm` service, no adapter for the route, or a catalog error), the disable\nis **explicitly refused and logged** — never silently treated as applied, which\nwould leave a model looking vision-off while still on. Adapter registration\noften trails this plugin's mount or a settings edit, so the plugin listens for\n`llm/adapters-updated`: a refused catalog disable retries automatically once\nthe adapter is ready instead of staying unapplied.\n\n### 0.1.3 packaging-only release note\n\n0.1.3 is a packaging-only release: identical plugin code and behavior to\n0.1.2. The 0.1.2 archive unintentionally carried an AppleDouble sidecar\n(`._cordis.patch.yml`) created by the platform tar tool from a local extended\nattribute; 0.1.3 is built with the maintained node-tar writer (`portable`),\nwhich never stores macOS filesystem metadata, so the archive contains exactly\nthe intended payload. The 0.1.2 release and its public archive remain\nimmutable history.\n\n### 0.1.2 fix-release note\n\n0.1.2 fixes a real-framework disposal bug: `apply` was a constructible\n`function`, so Cordis' fiber runner `new`ed it and discarded the async disposer\nit returns — the \"dispose awaits an in-flight `settings.replace`\" guarantee\nsilently never registered when the plugin was unloaded by the actual Loader.\n`apply` is now an arrow function (`export const apply = …`), which Cordis calls\ndirectly and whose disposer it collects and awaits. Behavior is otherwise\nidentical to 0.1.1; the 0.1.1 ownership/undo rules and the migration notes\nbelow all still apply. The regression is covered by a real-Cordis/Loader\nfixture test that holds a `settings.replace` in flight and proves the fiber\ndispose waits for it, with nothing written after disposal.\n\n### 0.1.0 → 0.1.1 migration notes\n\n0.1.0's automatic sync **deleted** user-authored `input: [text]` /\n`modelOverrides` and wrote its values **without provenance markers**\n(including route-level `defaultInput`). After upgrading, which values were\noriginally user-authored cannot be recovered: every unmarked existing value is\ntreated as \"user-owned\" and is never auto-removed by this plugin (setting\n`defaultImage` to `false` will not undo a route `defaultInput` that 0.1.0\nwrote). If 0.1.0 removed an explicit declaration of yours, restore it by hand\n(for example, rewrite a model's `input` as `['text']`).\n\n## Configuration\n\nIn the plugin entry's `config` (a manual `cordis.patch.yml` or your config\nsource):\n\n```yaml\n- insert:\n    - id: vision-control\n      name: vision-control\n      config:\n        defaultImage: true                      # vision on by default (defaults to true)\n        disabled:                               # per-model vision off (optional)\n          seawork: [ gpt-5.6-terra ]\n          grok: [ grok-imagine ]\n```\n\n- `defaultImage`: boolean, defaults to `true`. When `false`, route-level\n  defaults are not written, and **undoes** route `defaultInput` values this\n  plugin wrote earlier that still carry the marker (the schema's `['text']`\n  floor resumes); user-declared or 0.1.0-era unmarked values are unaffected.\n- `disabled`: an array of model ids (applies to every route), or a\n  provider-keyed map; `*` matches any route:\n\n  ```yaml\n  config:\n    disabled:\n      seawork: [ gpt-5.6-terra ]\n      '*': [ shared-model ]\n  ```\n\n## Behavior boundaries\n\n- Writes only the `llm-pi-ai` namespace's user configuration section; does not\n  read or write other namespaces and does not touch the main program.\n- Disable writes act only on **hand-declared-route model entries** or\n  **catalog-route `modelOverrides`**, never writing `modelOverrides` for a\n  model also listed in a `models` array (`llm-pi-ai` rejects that combination).\n- `llm-deepseek` runs through its own adapter and is outside this plugin's\n  scope.\n- This plugin is not a vision-content gate; it only keeps `llm-pi-ai`'s model\n  capability declarations (`input` / `defaultInput`) consistent with your\n  configuration intent.\n","zhMd":"# vision-control\n\n按模型管理 `llm-pi-ai` 图片视觉开关的插件（不动主程序、无 UI，纯配置平面）。\n\n版本：0.1.3\n\n## 作用\n\n通过 `dsh-settings` 命名空间写入 `llm-pi-ai` 的用户配置，做两件事：\n\n- **默认全开**：当一条路由既没有用户声明、也没有组合 `base` 层声明 `defaultInput`\n  时，写入路由级 `defaultInput: [text, image]`。已声明的 `defaultInput`（哪怕\n  `[text]`）一律不改写。\n- **手动关闭**：`disabled` 里的模型，对该模型写入 `input: [text]` 关闭视觉；\n  从 `disabled` 移除后，只撤销本插件自己写入的那一条。\n\n## 所有权与撤销规则（0.1.1 起）\n\n本插件只写自己\"拥有\"的字段，并用确定性的来源标记记录每次写入：\n\n- 关闭一个**手声明路由**（该路由配置里有 `models` 列表）的模型时，仅当该模型条目\n  **没有声明 `input`**，才写入 `input: [text]`，并同时写入来源标记\n  `_visionControl: { input: true }`。\n- 关闭一个 **catalog 路由**（无 `models`，由已安装 pi-ai 目录提供）的模型时，仅当\n  权威主机模型目录确实描述该模型的该路由，才写入\n  `modelOverrides.<id>: { input: [text], _visionControl: { input: true } }`。\n- 写入路由级 `defaultInput: [text, image]` 时，同时在路由对象上写来源标记\n  `_visionControl: { defaultInput: true }`。\n- 解除关闭、或把 `defaultImage` 设为 `false` 时，**只删除带本插件标记的写入**。\n  任何没有标记的 `input` / `defaultInput` —— 包括用户手写的 `input: [text]` ——\n  一律视为用户所有，绝不自动删除或改写。\n- 若用户在带标记的 `input` 或路由 `defaultInput` 上又手动改过值，本插件会放弃该\n  条目的所有权（删除标记、保留用户的值），不再自动撤销它。\n- 仅存在于解析层、用户从未声明过的路由，不会为其凭空生成用户配置。\n- 幂等：重复加载、重复 reconcile 无副作用；配置校验失败在加载时报错，不静默忽略。\n\n### Catalog 路由关闭的校验来源\n\n关闭 catalog 路由的模型前，本插件通过运行时的 `llm` 服务（`ctx.get('llm')`）核对\n权威目录：该服务的适配器为这条路由物化的模型集合，正是 `llm-pi-ai` 会接受\n`modelOverrides` 条目的集合。**无法核对**（无 `llm` 服务、该路由没有适配器、或目录\n查询出错）时，关闭会被**明确拒绝并记录警告**——绝不静默当作已生效，避免模型在\n视觉上看起来已关、实际仍开着。适配器注册通常晚于本插件挂载或晚于设置变更，因此\n本插件监听 `llm/adapters-updated`：一次被拒绝的 catalog 关闭会在适配器就绪后自动\n重试，不会一直停在未生效状态。\n\n### 0.1.3 纯打包版说明\n\n0.1.3 是纯打包版：插件代码与行为与 0.1.2 完全一致。0.1.2 的存档意外携带了一个\nAppleDouble 伴生文件（`._cordis.patch.yml`），由平台 tar 工具从本地扩展属性生成；\n0.1.3 改用受维护的 node-tar 写入器（`portable`）构建，绝不写入 macOS 文件系统元数据，\n因此存档只包含预期载荷。0.1.2 发布及其公共存档保持为不可变历史。\n\n### 0.1.2 修复版说明\n\n0.1.2 修复了一个真实框架下的销毁缺陷：`apply` 原本是带 `prototype` 的\n`function`，Cordis 的 fiber 运行器会把它当作构造函数 `new` 出来，于是它返回的\n异步 dispose 被**丢弃**——「dispose 会等待进行中的 `settings.replace`」这一保证在\n由真实 Loader 卸载插件时从未真正注册。`apply` 现改为箭头函数\n（`export const apply = …`）：Cordis 会直接调用它，并把返回的 dispose 收集、等待。\n其余行为与 0.1.1 完全一致，0.1.1 起的所有权/撤销规则与下方迁移说明均继续适用。\n该回归由真实 Cordis/Loader 组合夹具覆盖：测试让一次 `settings.replace` 停留在\n飞行中，证明 fiber dispose 会等待它落盘，且销毁后不再有任何写入。\n\n### 0.1.0 → 0.1.1 迁移注意\n\n0.1.0 的自动同步会**删除**用户手写的 `input: [text]` / `modelOverrides`，且写入的\n值**不带来源标记**（包括路由级 `defaultInput`）。升级后无法还原哪些值原本是用户\n手写的：无标记的现有值都会按\"用户所有\"处理、永不被本插件自动移除（即使把\n`defaultImage` 设为 `false` 也不会撤销 0.1.0 写入的路由 `defaultInput`）。若 0.1.0\n曾删掉你的显式声明，请手动补回（例如把某模型的 `input` 重新写成 `['text']`）。\n\n## 配置\n\n在插件行 `config` 中（手动 `cordis.patch.yml` 或你的配置来源）：\n\n```yaml\n- insert:\n    - id: vision-control\n      name: vision-control\n      config:\n        defaultImage: true                      # 默认全开（缺省 true）\n        disabled:                               # 手动关闭（可选）\n          seawork: [ gpt-5.6-terra ]\n          grok: [ grok-imagine ]\n```\n\n- `defaultImage`：布尔，缺省 `true`。设为 `false` 时不写路由级默认值，且**撤销**\n  之前由本插件写入、仍带来源标记的路由 `defaultInput`（恢复 schema 缺省的\n  `['text']`）；用户声明的或 0.1.0 遗留的无标记值不受影响。\n- `disabled`：模型 id 数组（作用于所有路由），或按路由分组的映射，支持\n  `*` 通配路由：\n\n  ```yaml\n  config:\n    disabled:\n      seawork: [ gpt-5.6-terra ]\n      '*': [ shared-model ]\n  ```\n\n## 行为边界\n\n- 只写 `llm-pi-ai` 命名空间的用户配置段；不读、不写其它命名空间，不触碰主程序。\n- 关闭写入只作用于**手声明路由的模型条目**或 **catalog 路由的 `modelOverrides`**，\n  绝不与 `models` 列表并写同一模型的 `modelOverrides`（`llm-pi-ai` 会拒绝该组合）。\n- `llm-deepseek` 走独立适配器，不在本插件范围内。\n- 本插件不做任何视觉内容闸门；它只保证 `llm-pi-ai` 的模型能力声明（`input` /\n  `defaultInput`）与你的配置意图一致。\n"},"versions":[{"version":"0.1.4","changelog":"","sha256":"097cb0b4c5e448440278f0a3147a130f67fdc409b1bdedd3078dab27247cef8a","byteSize":23861,"releasedAt":"2026-09-21T17:16:41.042Z","reviewStatus":"approved"},{"version":"0.1.3","changelog":"0.1.3: packaging-only release. The 0.1.2 archive unintentionally carried an AppleDouble sidecar (._cordis.patch.yml) from the platform tar tool","sha256":"949e85be6e05a947b9c21de13411595bc45459766f48f654d67bcdf4e11058c8","byteSize":23320,"releasedAt":"2026-09-08T11:20:12.321Z","reviewStatus":"approved"},{"version":"0.1.2","changelog":"0.1.2: apply is now a non-constructible arrow so Cordis fiber runner collects and awaits its disposer — unload waits for an in-flight settings.replace and writes nothing after dispose. Real-Cordis/Loader fixture regression test added.","sha256":"bd217bbcca8326104934fc1ad1446a0ce22bebda56520a8de7f1023f7acaf419","byteSize":22993,"releasedAt":"2026-09-08T09:41:39.736Z","reviewStatus":"approved"},{"version":"0.1.1","changelog":"0.1.1: ownership/provenance rewrite — write only what this plugin provably owns (marker-gated), verify catalog-route disables against the authoritative llm adapter, async dispose awaiting in-flight reconcile; lost-adapter re-arm + repeated-dispose fixed.","sha256":"c2092005a1eb218f0ca180ce0dfc5380fb475736bcfebd98949409ec11346bb3","byteSize":18546,"releasedAt":"2026-09-08T06:04:22.207Z","reviewStatus":"approved"},{"version":"0.1.0","changelog":"首个版本：默认全开 llm-pi-ai 各路由图片输入（defaultInput），支持按 provider/模型手动关闭（disabled），双向收敛与幂等。","sha256":"865f5c1e1fbfb2295b0055dec5bf28637154e3109bd7aad61adc305b1b33b844","byteSize":4745,"releasedAt":"2026-08-25T10:53:52.446Z","reviewStatus":"approved"}],"urls":{"html":"https://addon.dshdo.com/plugins/vision-control","markdown":"https://addon.dshdo.com/plugins/vision-control.md","json":"https://addon.dshdo.com/v1/plugins/vision-control","tarball":"https://addon.dshdo.com/v1/plugins/vision-control/versions/0.1.4/tarball"}}}