@liustack/modlens 3.16.6 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +4 -3
- package/README.zh-CN.md +5 -4
- package/dist/main.js +262 -46
- package/docs/cli.md +4 -3
- package/docs/cli.zh-CN.md +4 -3
- package/docs/harness-setup.md +32 -2
- package/docs/harness-setup.zh-CN.md +14 -2
- package/docs/security.md +2 -2
- package/docs/security.zh-CN.md +2 -2
- package/docs/troubleshooting.md +2 -2
- package/docs/troubleshooting.zh-CN.md +2 -2
- package/dsh/client.js +634 -1
- package/dsh/index.js +426 -8
- package/package.json +1 -1
- package/skills/modlens/SKILL.md +4 -4
- package/skills/modlens/references/configure.md +34 -8
- package/skills/modlens/references/configure.zh-CN.md +25 -8
- package/skills/modlens/references/onboard.md +1 -1
- 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/docs/harness-setup.md
CHANGED
|
@@ -55,18 +55,33 @@ 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.17.0
|
|
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.
|
|
62
62
|
|
|
63
|
+
### Configuring the engine from the web UI
|
|
64
|
+
|
|
65
|
+
dsh web users have no terminal in front of them, so the engine settings have a
|
|
66
|
+
card in **Settings → Plugins → Plugin configuration**: which engine reads
|
|
67
|
+
images, its key, endpoint and model, and which local sign-ins a read may
|
|
68
|
+
borrow. Expanding it probes this machine and lists the harnesses it actually
|
|
69
|
+
found, so the grants are a choice between real options rather than five names.
|
|
70
|
+
|
|
71
|
+
The values live where they always did, in `~/.modlens/config.json`, shared
|
|
72
|
+
with every other harness: the card reads and writes that file through a
|
|
73
|
+
loopback route, so an edit here is the same edit `modlens config set` makes.
|
|
74
|
+
The card never receives a stored key, only whether one is set, and leaving the
|
|
75
|
+
key field empty keeps the stored one. `settingsCard: false` in the plugin row
|
|
76
|
+
removes it, route included.
|
|
77
|
+
|
|
63
78
|
### Keeping it up to date
|
|
64
79
|
|
|
65
80
|
modlens ships often, and both install shapes freeze at whatever version they
|
|
66
81
|
got. On dsh, re-run the install with the version named:
|
|
67
82
|
|
|
68
83
|
```sh
|
|
69
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
84
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.17.0
|
|
70
85
|
```
|
|
71
86
|
|
|
72
87
|
`npm view @liustack/modlens version` prints the current one, and this page is
|
|
@@ -101,6 +116,21 @@ install-time version, so re-run the install to overwrite it in place.
|
|
|
101
116
|
behind the CLI doing the reporting, which makes the drift visible before it
|
|
102
117
|
costs anyone a debugging session.
|
|
103
118
|
|
|
119
|
+
### A session that already holds an image
|
|
120
|
+
|
|
121
|
+
dsh refuses to switch a session that contains image attachments to a model
|
|
122
|
+
whose declared modalities exclude images, which includes every plain text-only
|
|
123
|
+
DeepSeek entry. The rule is dsh's and it is sound: a text-only model cannot
|
|
124
|
+
receive a history carrying image blocks, and the `(modlens vision)` variant is
|
|
125
|
+
switchable there not because it declares image input but because it converts
|
|
126
|
+
those blocks to evidence text at request time, which the plain entry does not
|
|
127
|
+
do ([#40](https://github.com/liustack/modlens/issues/40)).
|
|
128
|
+
|
|
129
|
+
Pasting through the first route above avoids the situation entirely: the image
|
|
130
|
+
becomes a file path and the session never holds an attachment, so nothing locks
|
|
131
|
+
the model selector. It only comes up after a paste on a variant or on a vision
|
|
132
|
+
model, where an attachment is the point.
|
|
133
|
+
|
|
104
134
|
### Paste-to-path (web profile)
|
|
105
135
|
|
|
106
136
|
Pasting an image into the dsh Web UI under a **text-only model** used to die at
|
|
@@ -55,17 +55,23 @@ 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.17.0
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
这会注册一个 `modlens_read_image` 工具,它的 schema 随每次请求抵达模型(不靠触发启发式),运行同一个包里自带的 modlens CLI,并把结构化证据作为工具的标准 JSON 输出返回。引擎、复用授权和 guard 规则仍在 `~/.modlens/config.json` 里,与其他所有 harness 共享。dsh 还在开发者预览阶段,插件接口可能变化。这个插件刻意保持很小的接触面(原生工具注册、视觉变体所用的 llm 适配层、附件读取器,以及一个 agent 执行前钩子),其中任何一处变动,它都会大声报错而不是无声退化。
|
|
62
62
|
|
|
63
|
+
### 在网页界面里配置引擎
|
|
64
|
+
|
|
65
|
+
dsh 的网页用户面前没有终端,所以引擎设置有一张卡片,在**设置 → 插件 → 插件配置**里:用哪个引擎读图、它的密钥、地址和模型,以及一次读取可以借用本机哪些已有登录。展开时会探测本机,只列出真正找到的 harness,让授权是在真实选项之间做选择,而不是面对五个名字。
|
|
66
|
+
|
|
67
|
+
这些值仍然住在 `~/.modlens/config.json`,与其他所有 harness 共享:卡片通过一条回环路由读写那个文件,所以在这里改一笔,和 `modlens config set` 改的是同一笔。卡片从不拿到已保存的密钥,只知道有没有;密钥框留空就不动已存的那个。在插件配置行里设 `settingsCard: false` 可以连同路由一起去掉它。
|
|
68
|
+
|
|
63
69
|
### 保持更新
|
|
64
70
|
|
|
65
71
|
modlens 发布很频繁,而两种安装形态都会冻结在装进来的那个版本上。dsh 上重跑一遍安装即可,版本号要点名:
|
|
66
72
|
|
|
67
73
|
```sh
|
|
68
|
-
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.
|
|
74
|
+
npx -y @deepseek-ai/dsh plugin --profile <name> add @liustack/modlens@3.17.0
|
|
69
75
|
```
|
|
70
76
|
|
|
71
77
|
`npm view @liustack/modlens version` 可以查到当前版本号,本页的版本号则由发布流程自动写入。
|
|
@@ -82,6 +88,12 @@ npx -y @deepseek-ai/dsh plugin --profile <name> list
|
|
|
82
88
|
|
|
83
89
|
skill 类 harness 上,skill 是一个拷贝出来的文件夹,拷贝会保留安装时的版本,重跑安装原地覆盖即可。`modlens doctor` 会读出它能找到的每一份拷贝里钉住的版本,并标出落后于当前 CLI 的那些,让版本漂移在坑到人之前就先暴露出来。
|
|
84
90
|
|
|
91
|
+
### 会话里已经有图片时
|
|
92
|
+
|
|
93
|
+
会话里存在图片附件时,dsh 会拒绝切换到声明模态不含图片的模型,普通的 DeepSeek 纯文本条目都在此列。这条规则是 dsh 的,而且站得住:纯文本模型确实收不了带图片块的历史,而 `(modlens vision)` 变体之所以能切过去,靠的不是声明了图片输入,而是它在发请求时把那些块转成证据文本,普通条目没有这层转换([#40](https://github.com/liustack/modlens/issues/40))。
|
|
94
|
+
|
|
95
|
+
走上面第一条粘贴路线就不会遇到:图片变成文件路径,会话里从不存在附件,模型选择器也就不会被锁住。只有在变体或视觉模型上粘贴时才会产生附件,而那种场景下附件本来就是目的。
|
|
96
|
+
|
|
85
97
|
### 粘贴转路径(paste-to-path,web profile)
|
|
86
98
|
|
|
87
99
|
过去在 dsh Web UI 里,**纯文本模型**下粘贴图片会死在图片准入检查这一步。插件现在带了一个浏览器端半边(由 dsh 的客户端插件系统自动加载),恰好在这种情况下接管粘贴:图片字节发到插件在 dsh web 服务器上的 `/modlens/paste` 路由(仅回环地址,校验 magic byte,上限 25 MB),落成一个私有临时文件,输入框收到的则是纯文本的文件路径。这与 Pi、OpenCode、Claude Code 递给模型的形态一致,也正是 modlens skill 和 `modlens_read_image` 工具的首要触发条件。消息里不带图片附件,准入检查根本不会触发。
|
package/docs/security.md
CHANGED
|
@@ -20,9 +20,9 @@ Recovery is also scoped to one project: the working directory recorded inside th
|
|
|
20
20
|
|
|
21
21
|
ModLens invokes `agy` with `--dangerously-skip-permissions` because prompt mode fails in some environments without it. The prompt restricts the agent to reading the one image it was given, and instructs it to treat image content strictly as data.
|
|
22
22
|
|
|
23
|
-
The `claude-cli` provider runs with `--allowedTools Read` only, so it can read local files and nothing else.
|
|
23
|
+
The `claude-cli` provider runs with `--allowedTools Read` only, so it can read local files and nothing else. The `kimi-cli` provider cannot be narrowed that way (its CLI has no equivalent flag), so it is treated differently in two respects: it runs only when named, never as a failover peer, since it spends a subscription; and it runs with skill discovery pointed at an empty directory, because otherwise kimi can find the modlens skill and read the image by running modlens, which is modlens calling itself. Its child also carries a marker that makes a nested modlens refuse to spawn kimi again.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The subprocess providers all run in a throwaway directory created fresh per call and removed afterward. For a local image it holds a private copy of that one image and nothing else, and it is a real copy, never a hardlink, so a provider writing to its temp path cannot touch the original. For a remote image the directory is empty and the agent downloads into it. Without this, text inside an image could steer a broadly-permissioned agent into reading files next to the original, or whatever project the caller happened to be in. Passing `--workdir` opts out and runs where you point it.
|
|
26
26
|
|
|
27
27
|
This is exposure reduction, not an OS sandbox: the agent can still read absolute paths, reach the network, and spawn processes. Treat it as a narrower default, not a security boundary. For images you do not trust, prefer `-p gemini-api`, which downloads the bytes itself and runs no local agent. Remote URLs already prefer the inline region: the failover chain for a remote URL tries the inline API providers first and the agents last. Who actually fetches a remote URL differs per provider, and only a local download can be guarded locally:
|
|
28
28
|
|
package/docs/security.zh-CN.md
CHANGED
|
@@ -20,9 +20,9 @@ read_when:
|
|
|
20
20
|
|
|
21
21
|
ModLens 调用 `agy` 时带上 `--dangerously-skip-permissions`,因为某些环境下 prompt 模式不带它就会失败。prompt 把 agent 限制为只读交给它的那一张图片,并指示它把图片内容严格当作数据。
|
|
22
22
|
|
|
23
|
-
`claude-cli` provider 只带 `--allowedTools Read`
|
|
23
|
+
`claude-cli` provider 只带 `--allowedTools Read` 运行,因此它能读本地文件,别的什么都做不了。`kimi-cli` 没法这样收窄(它的 CLI 没有对应参数),所以在两处区别对待:它只在被点名时运行,绝不作为故障转移的备选,因为它花的是订阅;运行时把 skill 发现指向空目录,否则 kimi 会找到 modlens skill、通过运行 modlens 来读图,也就是 modlens 自己调自己。它的子进程还带一个标记,让嵌套的 modlens 拒绝再次启动 kimi。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
这些子进程 provider 还都运行在一个用完即弃的目录里,每次调用新建,结束后删除。本地图片时,目录里只有那张图片的一份私有副本,别无他物,而且是真实拷贝,绝不用硬链接,provider 往自己的临时路径写东西也碰不到原文件。远程图片时目录是空的,agent 把文件下载进去。没有这层隔离,图片里的文字就可能引导一个权限宽泛的 agent 去读原图旁边的文件,或读调用者恰好所在的项目。传 `--workdir` 即放弃这层隔离,在你指定的位置运行。
|
|
26
26
|
|
|
27
27
|
这是在缩小暴露面,不是操作系统沙箱:agent 仍然能读绝对路径、访问网络、启动进程。把它当作更收紧的默认值,而不是安全边界。不信任的图片优先用 `-p gemini-api`,它自己下载字节,不运行任何本地 agent。远程 URL 本来就优先走内联这一侧:远程 URL 的故障转移链先试内联的 API provider,agent 排在最后。远程 URL 实际由谁抓取因 provider 而异,而只有本地下载能被本地防护覆盖:
|
|
28
28
|
|
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.17.0
|
|
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.17.0'
|
|
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.17.0
|
|
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.16.6
|
|
|
153
153
|
|
|
154
154
|
```yaml
|
|
155
155
|
minimumReleaseAgeExclude:
|
|
156
|
-
- '@liustack/modlens@3.
|
|
156
|
+
- '@liustack/modlens@3.17.0'
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
或者只为这一条命令解除冷静期,注意它解除的是这条命令解析到的所有包,不只 modlens:
|