@liustack/modlens 3.24.2 → 3.25.1
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 +9 -1
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/main.js +18 -4
- package/docs/harness-setup.md +2 -2
- package/docs/harness-setup.zh-CN.md +2 -2
- package/docs/troubleshooting.md +2 -2
- package/docs/troubleshooting.zh-CN.md +2 -2
- package/dsh/client.js +48 -8
- package/dsh/index.js +11 -5
- package/package.json +1 -1
- package/skills/modlens/SKILL.md +4 -4
- package/skills/modlens/references/runtime.md +1 -1
- package/skills/modlens/scripts/run.ps1 +1 -1
- package/skills/modlens/scripts/run.sh +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.25.1 - 2026-08-27
|
|
4
|
+
|
|
5
|
+
- **Aborted provider calls fail over again on Node 24 ([#85](https://github.com/liustack/modlens/issues/85)).** Node 24's `DOMException` — including the `AbortError` thrown when `AbortSignal.timeout` fires or a call is cancelled — exposes `message` as a read-only getter, and two places assigned to it directly: the failover loop in `analyze()` while contextualizing a provider failure, and `runCommand()` while redacting secrets from the explained error. The assignment threw `Cannot set property message of #<AbortError> which has only a getter`, replacing the abort with a hard crash, so a timed-out provider took the whole read down instead of falling through to the next provider in the chain. On dsh this surfaced as "the vision engine failed" for any paste slow enough to hit the timeout. Both sites now write through one helper that assigns when it can and redefines the property on the same object when the getter refuses, so the error keeps its identity (`instanceof`, quota classification) and failover proceeds. Regression tests cover a real `DOMException('AbortError')` and both call paths. Thanks to @Dialong for a report that had already located both assignments and the fix.
|
|
6
|
+
- **dsh: wrapper-forwarded streams say who forwarded them ([#89](https://github.com/liustack/modlens/issues/89)).** A `(modlens vision)` route forwards the converted call back through `llm.stream`, which emits a second `llm/stream` event carrying the same usage under the upstream provider id — so chain-tail meters counted the call twice, and their `(turn, step)` dedupe was defeated by the differing provider ids. The forwarded options now carry `via: <wrapperProviderId>` (`deepseek-modlens` or `modlens-<upstream>`), making the contract explicit: a downstream listener that sees `options.via` is looking at the wrapper's forwarded hop and should account only the upstream stream. The key stays in-process — dsh applies no runtime schema to stream options, and adapters serialize only known fields into the HTTP request. Meters matching the `modlens-*` name prefix keep working; reading `via` is the supported spelling from here on. Thanks to @lucas-wang-1 for the design note.
|
|
7
|
+
|
|
8
|
+
## 3.25.0 - 2026-08-25
|
|
9
|
+
|
|
10
|
+
- **dsh: Xiaomi MiMo's text-only models get their `(modlens vision)` variants ([#80](https://github.com/liustack/modlens/issues/80), [#82](https://github.com/liustack/modlens/issues/82)).** Auto-discovery's default families now include `mimo`, but with the gate reversed: only ids carrying a `-pro` segment (`mimo-v2.5-pro`, `mimo-v2.5-pro-ultraspeed`, `mimo-v2-pro`, and a `:free`-style qualifier after it) are wrapped. Xiaomi's naming convention — verified against the official model catalog for both the v2 and v2.5 generations — marks bare version ids as native omni models (`mimo-v2.5` takes text, image, video, and audio) and `-pro` as the text-only flagship, so there is no vision marker in the name to exclude; only the named text subset is safe to include. The bare omni model keeps its native sight, and the `asr`/`tts`/`voiceclone`/`voicedesign` speech line falls outside the gate on its own. A declared image modality still vetoes a wrap first, and an explicit `families` list keeps the same MiMo gate. Thanks to @MagicSquarekey and @375578951 for the reports.
|
|
11
|
+
- **dsh: the settings card renders its form immediately instead of waiting out a full local-agent probe ([#83](https://github.com/liustack/modlens/issues/83)).** Expanding the card used to fetch `/modlens/config?discover=1`, and the route awaited a complete `doctor --json` sweep — every local harness probed, 30-second cap, painfully slow on Windows with `.cmd` shims and antivirus scanning — before the card showed anything but a spinner. The card now loads in two phases: a plain config fetch renders the engine, key, and model fields at once, and the reuse/auto section fills in lazily when its own `discover=1` request returns, showing its small loading note meanwhile. A collapsed card discards late responses instead of letting them overwrite newer state, re-expanding does not re-probe, a save no longer clobbers a probe that finished after it, and a failed probe falls back to the plain grant list as before. The discovery cache also grows from 60 seconds to 10 minutes, so re-opening the card within that window pays nothing. Thanks to @Ztyss for a report that had already located every link in the chain.
|
|
4
12
|
|
|
5
13
|
- **dsh: repeated `modlens_read_image` calls reuse one image read ([#81](https://github.com/liustack/modlens/issues/81)).** A small host model can emit the same path-tool call repeatedly inside its thinking loop, and every call started a new CLI process because #68 cached attachment conversion but not the explicit tool. The tool now keeps a per-plugin LRU cache keyed by the CLI-normalized source identity and focus prompt. Identical concurrent calls join the same pending read. A changed local file or focus creates a fresh read, remote results expire after 60 seconds, and failures cool for 60 seconds before retrying. Returned evidence is cloned so one consumer cannot mutate the shared result, and the model-facing description explicitly says to call once and reuse the evidence. This prevents repeated tool decisions from becoming repeated vision-provider work. It cannot make a small model stop hallucinating calls or recover reliably from every tool failure, so duplicate call events may remain visible and hosts should still cap tool rounds where available.
|
|
6
14
|
|
package/README.md
CHANGED
|
@@ -33,9 +33,9 @@ Issues are welcome any time: [open one](https://github.com/liustack/modlens/issu
|
|
|
33
33
|
|
|
34
34
|
## Highlights
|
|
35
35
|
|
|
36
|
-
**🥇 The most capable vision plugin for DeepSeek Harness (dsh):** install it instantly with one command: `npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.
|
|
36
|
+
**🥇 The most capable vision plugin for DeepSeek Harness (dsh):** install it instantly with one command: `npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.25.1`. See the [setup guide](docs/harness-setup.md) for installation and update details. If the command line is not your thing but you still want to try DSH, check out <a href="https://github.com/liustack/aimanager"><b>AIManager</b></a>, the lightest desktop wrapper for DeepSeek Harness. It gets you started with zero code or configuration and installs every dependency for you with one click.
|
|
37
37
|
|
|
38
|
-
Pasting an image works two ways. **① Just paste.** On a text-only model the pasted image lands as a private temp file and its path enters the composer
|
|
38
|
+
Pasting an image works two ways. **① Just paste.** On a text-only model the pasted image lands as a private temp file and its path enters the composer (the same interaction OpenCode and Pi ship), then the `modlens_read_image` tool takes it from there. **② Pick a `(modlens vision)` entry** in the model selector (it remembers your choice, so once is enough), then paste: the thumbnail stays visible in your message, closer to the Codex app feel, and the image is converted to structured evidence at request time, answered by the same underlying route. The plugin auto-discovers every provider route carrying eligible text-only DeepSeek, GLM, or MiMo Pro models and adds a wrapped entry per route. A stock install gets **`DeepSeek-V4-Flash (modlens vision)`** and **`DeepSeek-V4-Pro (modlens vision)`**, while extra routes like opencode-go or zai get their own. Native vision models in those families are excluded automatically. Which paste route applies is the host's per-model call: only a model its metadata positively confirms text-only is taken over, anything unconfirmed is left alone, so vision models keep their native paste ([details](docs/harness-setup.md)).
|
|
39
39
|
|
|
40
40
|
**Paste images directly in every harness.** No saving to a file and passing a path first.
|
|
41
41
|
|
package/README.zh-CN.md
CHANGED
|
@@ -33,7 +33,7 @@ DeepSeek 和 GLM 的主力对话模型是纯文本的,无法进行图片识别
|
|
|
33
33
|
|
|
34
34
|
## 亮点
|
|
35
35
|
|
|
36
|
-
**🥇 全网最强的 DeepSeek Harness(dsh)外挂视觉识别插件:**一条命令即刻安装 `npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.
|
|
36
|
+
**🥇 全网最强的 DeepSeek Harness(dsh)外挂视觉识别插件:**一条命令即刻安装 `npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.25.1`。更多安装与更新细节参考 [配置手册](docs/harness-setup.zh-CN.md) 。如果用不惯命令行,也想想玩玩 DSH,推荐食用全网最轻量级的 DeepSeek Harness 桌面版封装 <a href="https://github.com/liustack/aimanager"><b> AIManager</b></a>,零代码零配置起手,一键帮你安装所有依赖环境。
|
|
37
37
|
|
|
38
38
|
DeepSeek Harness 粘贴识图有两种玩法。
|
|
39
39
|
|
|
@@ -71,7 +71,7 @@ agy # 浏览器完成
|
|
|
71
71
|
**DeepSeek Harness(dsh)用户不走 skill 流程**,本包就是原生 dsh 插件:
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.
|
|
74
|
+
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.25.1
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
装完即有 `modlens_read_image` 工具,选「(modlens vision)」模型变体即可直接粘贴识图。引擎配置同样在 `~/.modlens`,详见[宿主接入](docs/harness-setup.zh-CN.md)。
|
package/dist/main.js
CHANGED
|
@@ -3726,6 +3726,20 @@ function buildCooldownController(config2, opts = {}) {
|
|
|
3726
3726
|
}
|
|
3727
3727
|
};
|
|
3728
3728
|
}
|
|
3729
|
+
function setErrorMessage(error, message) {
|
|
3730
|
+
try {
|
|
3731
|
+
error.message = message;
|
|
3732
|
+
} catch (caught) {
|
|
3733
|
+
if (!(caught instanceof TypeError)) {
|
|
3734
|
+
throw caught;
|
|
3735
|
+
}
|
|
3736
|
+
Object.defineProperty(error, "message", {
|
|
3737
|
+
value: message,
|
|
3738
|
+
configurable: true,
|
|
3739
|
+
writable: true
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3729
3743
|
const DEFAULT_TIMEOUT_MS = 18e4;
|
|
3730
3744
|
const KILL_GRACE_MS = 3e4;
|
|
3731
3745
|
const DRAIN_GRACE_MS = 500;
|
|
@@ -3833,7 +3847,7 @@ async function analyzeImage(options) {
|
|
|
3833
3847
|
apiKeys
|
|
3834
3848
|
);
|
|
3835
3849
|
if (error instanceof Error) {
|
|
3836
|
-
error
|
|
3850
|
+
setErrorMessage(error, message);
|
|
3837
3851
|
}
|
|
3838
3852
|
lastError = error;
|
|
3839
3853
|
attempts.push({
|
|
@@ -4190,7 +4204,7 @@ function runCommand(providerName, invocation, timeoutMs, describeFailure) {
|
|
|
4190
4204
|
if (code !== 0) {
|
|
4191
4205
|
const explained = describeFailure?.({ stdout, stderr, code, startedAt: runStartedAt }) ?? null;
|
|
4192
4206
|
if (explained instanceof Error) {
|
|
4193
|
-
explained
|
|
4207
|
+
setErrorMessage(explained, redactSecrets(explained.message));
|
|
4194
4208
|
reject(explained);
|
|
4195
4209
|
return;
|
|
4196
4210
|
}
|
|
@@ -5520,7 +5534,7 @@ function parsePositiveInt(raw, flag) {
|
|
|
5520
5534
|
}
|
|
5521
5535
|
return Number.parseInt(raw, 10);
|
|
5522
5536
|
}
|
|
5523
|
-
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.
|
|
5537
|
+
program.name("modlens").description("Plug-in vision for text-only LLMs: image in, structured JSON evidence out").version("3.25.1");
|
|
5524
5538
|
program.command("analyze", { isDefault: true }).description("Analyze an image into structured JSON evidence (default command)").requiredOption("-i, --input <path|url>", "Input image path or https URL").option("-o, --output <path>", "Write result JSON to a file").option("-m, --model <name>", "Provider model name").option("-p, --provider <name>", `Vision provider (${listProviders().join(", ")})`).option("--prompt <text>", "Extra focus for this image").option("--timeout <ms>", "Provider timeout in milliseconds", "180000").option("--provider-bin <path>", "Provider binary path (default: agy)").option("--workdir <path>", "Working directory for the provider").option(
|
|
5525
5539
|
"--extra-body <json>",
|
|
5526
5540
|
`JSON merged into the API request body, e.g. '{"thinking":{"type":"disabled"}}'`
|
|
@@ -5631,7 +5645,7 @@ program.command("doctor").description(
|
|
|
5631
5645
|
configPath: CONFIG_PATH,
|
|
5632
5646
|
// Lets doctor name an installed skill copy that is older than
|
|
5633
5647
|
// the CLI reporting on it (issue #33).
|
|
5634
|
-
version: "3.
|
|
5648
|
+
version: "3.25.1"
|
|
5635
5649
|
});
|
|
5636
5650
|
const output = options.json ? JSON.stringify(report, null, 2) : renderDoctorReport(report);
|
|
5637
5651
|
process.stdout.write(`${output}
|
package/docs/harness-setup.md
CHANGED
|
@@ -55,7 +55,7 @@ OpenCode with DeepSeek: `opencode auth login`, pick DeepSeek and paste the key (
|
|
|
55
55
|
dsh is different from the other harnesses: modlens plugs in as a native tool, not a prompt-triggered skill. The package itself is a dsh bundle, so one command installs it into a profile:
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.
|
|
58
|
+
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.25.1
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
This registers a `modlens_read_image` tool whose schema reaches the model on every request (no trigger heuristics), runs the modlens CLI shipped inside the same package, and returns the structured evidence as the tool's canonical JSON output. Engines, reuse grants, and guard rules stay in `~/.modlens/config.json`, shared with every other harness. dsh is in developer preview and its plugin surface may change; the plugin keeps its touch small (raw tool registration, the llm adapter surface for the vision variants, the attachment reader, and one agent pre-step hook) and degrades loudly if any of them moves.
|
|
@@ -87,7 +87,7 @@ modlens ships often, and both install shapes freeze at whatever version they
|
|
|
87
87
|
got. On dsh, re-run the install with the version named:
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
90
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.25.1
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
`npm view @liustack/modlens version` prints the current one, and this page is
|
|
@@ -55,7 +55,7 @@ OpenCode 接 DeepSeek:执行 `opencode auth login`,选择 DeepSeek 并粘贴
|
|
|
55
55
|
dsh 与其他 harness 不同:modlens 以原生工具的形式接入,而不是靠提示词触发的 skill。本包自身就是一个 dsh bundle,一条命令即可装进某个 profile:
|
|
56
56
|
|
|
57
57
|
```sh
|
|
58
|
-
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.
|
|
58
|
+
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.25.1
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
这会注册一个 `modlens_read_image` 工具,它的 schema 随每次请求抵达模型(不靠触发启发式),运行同一个包里自带的 modlens CLI,并把结构化证据作为工具的标准 JSON 输出返回。引擎、复用授权和 guard 规则仍在 `~/.modlens/config.json` 里,与其他所有 harness 共享。dsh 还在开发者预览阶段,插件接口可能变化。这个插件刻意保持很小的接触面(原生工具注册、视觉变体所用的 llm 适配层、附件读取器,以及一个 agent 执行前钩子),其中任何一处变动,它都会大声报错而不是无声退化。
|
|
@@ -73,7 +73,7 @@ dsh 的网页用户面前没有终端,所以引擎设置有一张卡片,在*
|
|
|
73
73
|
modlens 发布很频繁,而两种安装形态都会冻结在装进来的那个版本上。dsh 上重跑一遍安装即可,版本号要点名:
|
|
74
74
|
|
|
75
75
|
```sh
|
|
76
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
76
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.25.1
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
`npm view @liustack/modlens version` 可以查到当前版本号,本页的版本号则由发布流程自动写入。
|
package/docs/troubleshooting.md
CHANGED
|
@@ -163,7 +163,7 @@ simply lands on an older one. Name the exact version instead, which pnpm treats
|
|
|
163
163
|
as a deliberate request rather than a resolution:
|
|
164
164
|
|
|
165
165
|
```sh
|
|
166
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
166
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.25.1
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
`npm view @liustack/modlens version` prints the current one. pnpm 11 installs a named
|
|
@@ -178,7 +178,7 @@ file:
|
|
|
178
178
|
|
|
179
179
|
```yaml
|
|
180
180
|
minimumReleaseAgeExclude:
|
|
181
|
-
- '@liustack/modlens@3.
|
|
181
|
+
- '@liustack/modlens@3.25.1'
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
Or lift the gate for a single command, which lifts it for everything that
|
|
@@ -144,7 +144,7 @@ dsh profile 装到的是旧版 modlens。`dsh.bundle` 声明从 3.9.0 起才存
|
|
|
144
144
|
`@latest` 绕不开这一层,本页早先的说法是错的。冷静期先把候选版本过滤掉,dist-tag 才在剩下的里面解析,于是它直接落到了更旧的那个上。改成写死精确版本号,pnpm 会把它当作一次明确的指定,而不是一次解析:
|
|
145
145
|
|
|
146
146
|
```sh
|
|
147
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
147
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.25.1
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
`npm view @liustack/modlens version` 可以查到当前版本号。pnpm 11 会装上被点名的版本,11.1.3 起还会把它作为一条已批准的例外写进该 profile 的 `pnpm-workspace.yaml`,其余所有包和 modlens 以后的版本仍然留在窗口后面。
|
|
@@ -153,7 +153,7 @@ npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.24.2
|
|
|
153
153
|
|
|
154
154
|
```yaml
|
|
155
155
|
minimumReleaseAgeExclude:
|
|
156
|
-
- '@liustack/modlens@3.
|
|
156
|
+
- '@liustack/modlens@3.25.1'
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
或者只为这一条命令解除冷静期,注意它解除的是这条命令解析到的所有包,不只 modlens:
|
package/dsh/client.js
CHANGED
|
@@ -381,6 +381,11 @@ window.__ModuleLoader__.load({
|
|
|
381
381
|
function ConfigCard(react, ui, localeRef) {
|
|
382
382
|
var h = react.createElement
|
|
383
383
|
var Input = ui.Input
|
|
384
|
+
// Each load claims a generation. Collapsing before the config arrives
|
|
385
|
+
// invalidates that load because reopening starts a fresh one. Once the
|
|
386
|
+
// form exists, its in-flight discovery stays relevant across collapse.
|
|
387
|
+
// The counter survives renders because ConfigCard is built once.
|
|
388
|
+
var gen = 0
|
|
384
389
|
|
|
385
390
|
// Built once per card so useSyncExternalStore is not handed a new
|
|
386
391
|
// subscribe on every render, which would resubscribe every render.
|
|
@@ -446,9 +451,11 @@ window.__ModuleLoader__.load({
|
|
|
446
451
|
var seed = (next, provider, keepReuse) => nextDraft(next, provider, keepReuse)
|
|
447
452
|
|
|
448
453
|
var load = react.useCallback(() => {
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
|
|
454
|
+
// Config first, so the engine form can render. Discovery is the
|
|
455
|
+
// self-check probing which local harnesses exist to be borrowed,
|
|
456
|
+
// paid after the form is up, cached host-side.
|
|
457
|
+
var id = ++gen
|
|
458
|
+
fetch('/modlens/config')
|
|
452
459
|
.then((r) =>
|
|
453
460
|
r.json().then((body) => {
|
|
454
461
|
if (!r.ok) throw new Error(body.error || '')
|
|
@@ -456,11 +463,38 @@ window.__ModuleLoader__.load({
|
|
|
456
463
|
}),
|
|
457
464
|
)
|
|
458
465
|
.then((next) => {
|
|
466
|
+
if (id !== gen) return
|
|
459
467
|
summaryState[1](next)
|
|
460
468
|
draftState[1](seed(next, next.provider))
|
|
461
469
|
noteState[1]('')
|
|
470
|
+
return fetch('/modlens/config?discover=1')
|
|
471
|
+
.then((r) =>
|
|
472
|
+
r.json().then((body) => {
|
|
473
|
+
if (!r.ok) throw new Error(body.error || '')
|
|
474
|
+
return body
|
|
475
|
+
}),
|
|
476
|
+
)
|
|
477
|
+
.then((discovered) => {
|
|
478
|
+
if (id !== gen) return
|
|
479
|
+
summaryState[1]((prev) => {
|
|
480
|
+
if (!prev) return prev
|
|
481
|
+
var merged = Object.assign({}, prev)
|
|
482
|
+
merged.discovery = discovered && 'discovery' in discovered ? discovered.discovery : null
|
|
483
|
+
return merged
|
|
484
|
+
})
|
|
485
|
+
})
|
|
486
|
+
.catch(() => {
|
|
487
|
+
if (id !== gen) return
|
|
488
|
+
summaryState[1]((prev) => {
|
|
489
|
+
if (!prev) return prev
|
|
490
|
+
var merged = Object.assign({}, prev)
|
|
491
|
+
merged.discovery = null
|
|
492
|
+
return merged
|
|
493
|
+
})
|
|
494
|
+
})
|
|
462
495
|
})
|
|
463
496
|
.catch((error) => {
|
|
497
|
+
if (id !== gen) return
|
|
464
498
|
noteState[1](noteFrom(error, t.loadFailed))
|
|
465
499
|
})
|
|
466
500
|
}, [])
|
|
@@ -704,7 +738,7 @@ window.__ModuleLoader__.load({
|
|
|
704
738
|
h(
|
|
705
739
|
'div',
|
|
706
740
|
{ style: { display: 'flex', flexWrap: 'wrap', gap: '10px 18px', paddingTop: '2px' } },
|
|
707
|
-
autoRows,
|
|
741
|
+
'discovery' in summary ? autoRows : t.loading,
|
|
708
742
|
),
|
|
709
743
|
'auto',
|
|
710
744
|
t.autoTitle,
|
|
@@ -801,10 +835,15 @@ window.__ModuleLoader__.load({
|
|
|
801
835
|
}),
|
|
802
836
|
)
|
|
803
837
|
.then((next) => {
|
|
804
|
-
// The save response carries no discovery
|
|
805
|
-
//
|
|
806
|
-
|
|
807
|
-
summaryState[1](
|
|
838
|
+
// The save response carries no discovery. Read the
|
|
839
|
+
// current state here because the lazy probe may have
|
|
840
|
+
// landed after this save began.
|
|
841
|
+
summaryState[1]((prev) => {
|
|
842
|
+
if (!prev || !('discovery' in prev)) return next
|
|
843
|
+
var merged = Object.assign({}, next)
|
|
844
|
+
merged.discovery = prev.discovery
|
|
845
|
+
return merged
|
|
846
|
+
})
|
|
808
847
|
draftState[1](seed(next, next.provider))
|
|
809
848
|
noteState[1](t.saved)
|
|
810
849
|
})
|
|
@@ -851,6 +890,7 @@ window.__ModuleLoader__.load({
|
|
|
851
890
|
type: 'button',
|
|
852
891
|
'aria-expanded': open,
|
|
853
892
|
onClick: () => {
|
|
893
|
+
if (open && summary === null) gen += 1
|
|
854
894
|
openState[1](!open)
|
|
855
895
|
},
|
|
856
896
|
style: {
|
package/dsh/index.js
CHANGED
|
@@ -664,7 +664,7 @@ function registerVisionProvider(ctx, config, ownProviders, evidenceCache) {
|
|
|
664
664
|
// wrapper twin loses its native sight. Family matching also strips a vendor
|
|
665
665
|
// namespace (OpenRouter's z-ai/glm-5.2:free, ~-prefixed aliases), because
|
|
666
666
|
// the text-only member is the same model wherever the id carries a prefix.
|
|
667
|
-
const families = config.families || ['deepseek', 'glm']
|
|
667
|
+
const families = config.families || ['deepseek', 'glm', 'mimo']
|
|
668
668
|
const VISION_ID = /(deepseek-(vl|ocr)|janus|glm-[\d.]*v(\b|-)|\bvision\b)/i
|
|
669
669
|
const shouldWrap = (info) => {
|
|
670
670
|
const id = String(info?.id ?? '').toLowerCase()
|
|
@@ -676,6 +676,10 @@ function registerVisionProvider(ctx, config, ownProviders, evidenceCache) {
|
|
|
676
676
|
if (!families.some((family) => id.startsWith(family) || bare.startsWith(family))) return false
|
|
677
677
|
if (VISION_ID.test(bare)) return false
|
|
678
678
|
if (Array.isArray(info?.inputModalities) && info.inputModalities.includes('image')) return false
|
|
679
|
+
// MiMo needs the gate reversed. Xiaomi's bare version ids name native
|
|
680
|
+
// omni models, while a -pro segment marks text-only flagships. There is no
|
|
681
|
+
// vision marker to exclude, so only the named text subset is safe to include.
|
|
682
|
+
if (bare.startsWith('mimo') && !/(^|-)pro(?:-|:|$)/i.test(bare)) return false
|
|
679
683
|
return true
|
|
680
684
|
}
|
|
681
685
|
// Keep this activation bound to the exact service implementation that made
|
|
@@ -812,7 +816,9 @@ function registerVisionProvider(ctx, config, ownProviders, evidenceCache) {
|
|
|
812
816
|
return (async function* () {
|
|
813
817
|
const converted = await convertImagesToEvidence(ctx, options.messages, options.signal, self)
|
|
814
818
|
const messages = restoreUpstreamSource(converted, providerId, upstream)
|
|
815
|
-
|
|
819
|
+
// Downstream meters can skip this hop when options.via names the
|
|
820
|
+
// wrapper that forwarded it, and count only the upstream stream (#89).
|
|
821
|
+
yield* llm.stream({ ...options, provider: upstream, messages, via: providerId })
|
|
816
822
|
})()
|
|
817
823
|
},
|
|
818
824
|
evidenceCache,
|
|
@@ -1773,10 +1779,10 @@ function applyEngineSettings(patch) {
|
|
|
1773
1779
|
* The self-check behind the card's auto-mode section: which local harnesses
|
|
1774
1780
|
* exist to be borrowed at all. `doctor --json` already probes them without
|
|
1775
1781
|
* network or quota, so the route spawns the CLI this package ships and lifts
|
|
1776
|
-
* its reuse section. Cached
|
|
1777
|
-
* re-expanding the card should not re-pay it.
|
|
1782
|
+
* its reuse section. Cached for ten minutes, since one probe can take a
|
|
1783
|
+
* second and re-expanding the card should not re-pay it.
|
|
1778
1784
|
*/
|
|
1779
|
-
const DISCOVERY_TTL_MS =
|
|
1785
|
+
const DISCOVERY_TTL_MS = 600_000
|
|
1780
1786
|
let discoveryCache = null
|
|
1781
1787
|
async function discoverReuse() {
|
|
1782
1788
|
if (discoveryCache !== null && Date.now() - discoveryCache.at < DISCOVERY_TTL_MS) {
|
package/package.json
CHANGED
package/skills/modlens/SKILL.md
CHANGED
|
@@ -20,11 +20,11 @@ powershell -ExecutionPolicy Bypass -File <skill-dir>\scripts\run.ps1 <args>
|
|
|
20
20
|
|
|
21
21
|
It resolves a working runtime (PATH `modlens`, then `npx`, then `bunx`) and forwards your arguments unchanged. Exit 78 means no runtime: relay the `nextSteps` from its stderr JSON instead of retrying.
|
|
22
22
|
|
|
23
|
-
If your harness forbids running scripts, reason through the same order by hand and run the first line that works (the pinned version is 3.
|
|
23
|
+
If your harness forbids running scripts, reason through the same order by hand and run the first line that works (the pinned version is 3.25.1):
|
|
24
24
|
|
|
25
|
-
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.
|
|
26
|
-
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.
|
|
27
|
-
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.
|
|
25
|
+
1. A `modlens` on `PATH` whose major version is 3 and is at least 3.25.1: `modlens <args>`.
|
|
26
|
+
2. Otherwise, if `npx` exists: `npx --yes --package @liustack/modlens@3.25.1 modlens <args>`.
|
|
27
|
+
3. Otherwise, if `bunx` exists: `bunx --bun @liustack/modlens@3.25.1 <args>`.
|
|
28
28
|
4. Otherwise tell the user no JavaScript runtime was found and that installing Node 22.19+ (https://nodejs.org) or Bun (https://bun.sh) is the next step. Do not claim modlens itself failed.
|
|
29
29
|
|
|
30
30
|
`references/runtime.md` documents the pin and the diagnostic fields.
|
|
@@ -24,7 +24,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
24
24
|
# package.json version, and the release script rewrites it on every bump.
|
|
25
25
|
$Package = '@liustack/modlens'
|
|
26
26
|
$Bin = 'modlens'
|
|
27
|
-
$Pinned = '3.
|
|
27
|
+
$Pinned = '3.25.1'
|
|
28
28
|
# -------------------------------------------------------------------------------
|
|
29
29
|
|
|
30
30
|
$NativeNote = 'no native artifact is published for this tool yet; phase A ships npm launch paths only'
|
|
@@ -22,7 +22,7 @@ set -eu
|
|
|
22
22
|
# package.json version, and the release script rewrites it on every bump.
|
|
23
23
|
PKG="@liustack/modlens"
|
|
24
24
|
BIN="modlens"
|
|
25
|
-
PINNED="3.
|
|
25
|
+
PINNED="3.25.1"
|
|
26
26
|
# -------------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
NATIVE_NOTE="no native artifact is published for this tool yet; phase A ships npm launch paths only"
|