dsh-zotero 0.1.0 → 0.2.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/README.en.md +64 -40
- package/README.md +64 -40
- package/lib/ask.d.ts +37 -0
- package/lib/ask.d.ts.map +1 -0
- package/lib/ask.js +130 -0
- package/lib/ask.js.map +1 -0
- package/lib/attachments.d.ts.map +1 -1
- package/lib/attachments.js +2 -10
- package/lib/attachments.js.map +1 -1
- package/lib/client.js +15350 -153
- package/lib/client.js.map +7 -1
- package/lib/config.d.ts +10 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +8 -1
- package/lib/config.js.map +1 -1
- package/lib/constants.d.ts +2 -0
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +2 -0
- package/lib/constants.js.map +1 -1
- package/lib/contract.d.ts +57 -0
- package/lib/contract.d.ts.map +1 -0
- package/lib/contract.js +109 -0
- package/lib/contract.js.map +1 -0
- package/lib/errors.d.ts +7 -6
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +21 -12
- package/lib/errors.js.map +1 -1
- package/lib/{client.d.ts → http-client.d.ts} +2 -2
- package/lib/http-client.d.ts.map +1 -0
- package/lib/http-client.js +230 -0
- package/lib/http-client.js.map +1 -0
- package/lib/json.d.ts +13 -0
- package/lib/json.d.ts.map +1 -0
- package/lib/json.js +22 -0
- package/lib/json.js.map +1 -0
- package/lib/normalize.d.ts +27 -13
- package/lib/normalize.d.ts.map +1 -1
- package/lib/normalize.js +118 -37
- package/lib/normalize.js.map +1 -1
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +6 -3
- package/lib/prompt.js.map +1 -1
- package/lib/provider-local.d.ts +53 -7
- package/lib/provider-local.d.ts.map +1 -1
- package/lib/provider-local.js +238 -59
- package/lib/provider-local.js.map +1 -1
- package/lib/refs.d.ts.map +1 -1
- package/lib/refs.js +2 -1
- package/lib/refs.js.map +1 -1
- package/lib/remote.d.ts +50 -0
- package/lib/remote.d.ts.map +1 -0
- package/lib/remote.js +104 -0
- package/lib/remote.js.map +1 -0
- package/lib/service.d.ts +28 -2
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +106 -24
- package/lib/service.js.map +1 -1
- package/lib/settings-namespace.d.ts +14 -0
- package/lib/settings-namespace.d.ts.map +1 -0
- package/lib/settings-namespace.js +14 -0
- package/lib/settings-namespace.js.map +1 -0
- package/lib/tools/attachment.d.ts.map +1 -1
- package/lib/tools/attachment.js +2 -1
- package/lib/tools/attachment.js.map +1 -1
- package/lib/tools/export.d.ts +9 -3
- package/lib/tools/export.d.ts.map +1 -1
- package/lib/tools/export.js +16 -8
- package/lib/tools/export.js.map +1 -1
- package/lib/tools/get.d.ts +20 -3
- package/lib/tools/get.d.ts.map +1 -1
- package/lib/tools/get.js +24 -12
- package/lib/tools/get.js.map +1 -1
- package/lib/tools/present.d.ts +16 -0
- package/lib/tools/present.d.ts.map +1 -0
- package/lib/tools/present.js +18 -0
- package/lib/tools/present.js.map +1 -0
- package/lib/tools/retrieve.d.ts +26 -5
- package/lib/tools/retrieve.d.ts.map +1 -1
- package/lib/tools/retrieve.js +36 -22
- package/lib/tools/retrieve.js.map +1 -1
- package/lib/tools/search.d.ts +12 -3
- package/lib/tools/search.d.ts.map +1 -1
- package/lib/tools/search.js +38 -23
- package/lib/tools/search.js.map +1 -1
- package/lib/tools/validate.d.ts +17 -0
- package/lib/tools/validate.d.ts.map +1 -0
- package/lib/tools/validate.js +24 -0
- package/lib/tools/validate.js.map +1 -0
- package/lib/typert.d.ts +16 -0
- package/lib/typert.d.ts.map +1 -0
- package/lib/typert.js +51 -0
- package/lib/typert.js.map +1 -0
- package/lib/types.d.ts +17 -2
- package/lib/types.d.ts.map +1 -1
- package/package.json +42 -7
- package/lib/client.d.ts.map +0 -1
package/README.en.md
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">dsh-zotero</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<b>English</b> · <a href="README.md"><b>中文</b></a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome DSH Plugin"></a>
|
|
9
|
+
<img src="https://img.shields.io/npm/v/dsh-zotero" alt="npm version">
|
|
10
|
+
<img src="https://img.shields.io/npm/dm/dsh-zotero" alt="npm downloads">
|
|
11
|
+
<img src="https://img.shields.io/npm/l/dsh-zotero" alt="license">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
7
14
|
Let agents search, read, and cite your local [Zotero](https://www.zotero.org) library: find papers, browse notes and annotations, pull evidence by question, open the source document, generate citations.
|
|
8
15
|
|
|
9
16
|
Describe what you need in a session and the Agent calls the tools below as needed. The only manual command is `/zotero status`.
|
|
10
17
|
|
|
11
18
|
## Tools
|
|
12
19
|
|
|
13
|
-
| Tool | Purpose
|
|
14
|
-
| ------------------- |
|
|
15
|
-
| `zotero_search` | Discover: search
|
|
16
|
-
| `zotero_get` | Inspect: read one item's structured core metadata, optionally with manifests and previews of its notes, annotations, and attachments.
|
|
17
|
-
| `zotero_retrieve` | Evidence: return the most relevant bounded evidence passages (annotations, notes, abstract, full-text chunks) for a query.
|
|
18
|
-
| `zotero_attachment` | Source: resolve an item or attachment ref to the original attachment's verified on-disk path or linked URL.
|
|
19
|
-
| `zotero_export` | Cite: let Zotero's own citation/export machinery produce citations, a CSL bibliography, or `bibtex` / `biblatex` / `ris` / `csljson`.
|
|
20
|
-
|
|
21
|
-
Every tool returns reusable refs of the form `zotero://user/0/<item|attachment|annotation|collection|search>/<KEY>`, optionally qualified with `?server=<id>`. Later calls chain through these refs. The Zotero 10+ `server` qualifier binds a ref to the database that produced it, so a database switch blocks stale refs instead of misreading them.
|
|
20
|
+
| Tool | Purpose |
|
|
21
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `zotero_search` | Discover: search by title/creator/year, or indexed full text with `everything`; optionally scope to a collection or saved search. |
|
|
23
|
+
| `zotero_get` | Inspect: read one item's structured core metadata, optionally with manifests and previews of its notes, annotations, and attachments. |
|
|
24
|
+
| `zotero_retrieve` | Evidence: return the most relevant bounded evidence passages (annotations, notes, abstract, full-text chunks) for a query. |
|
|
25
|
+
| `zotero_attachment` | Source: resolve an item or attachment ref to the original attachment's verified on-disk path or linked URL. |
|
|
26
|
+
| `zotero_export` | Cite: let Zotero's own citation/export machinery produce citations, a CSL bibliography, or `bibtex` / `biblatex` / `ris` / `csljson`. |
|
|
22
27
|
|
|
23
28
|
## Usage example
|
|
24
29
|
|
|
@@ -46,13 +51,6 @@ The Agent moves down the ladder as a request deepens. A typical conversation:
|
|
|
46
51
|
|
|
47
52
|
`/zotero status` reports connectivity, API/schema versions, and the database identity (Server ID, Zotero 10+). This is the only health check. Ordinary calls fail with typed domain errors.
|
|
48
53
|
|
|
49
|
-
## Limits
|
|
50
|
-
|
|
51
|
-
- Read-only library: V1 has no path that modifies items, notes, tags, or collections.
|
|
52
|
-
- Full-text evidence depends on Zotero's index: `everything` search and `retrieve` full-text passages both require indexing.
|
|
53
|
-
- Attachment depth depends on the harness composition: `zotero_attachment` returns the file location; reading that PDF further needs a matching file/PDF capability.
|
|
54
|
-
- Evidence ranking is term-based relevance, not embedding or semantic search.
|
|
55
|
-
|
|
56
54
|
## Requirements
|
|
57
55
|
|
|
58
56
|
- Zotero desktop with the local API enabled: **Settings → Advanced → "Allow other applications on this computer to communicate with Zotero"**.
|
|
@@ -67,7 +65,7 @@ The Agent moves down the ladder as a request deepens. A typical conversation:
|
|
|
67
65
|
dsh plugin --profile <name> add dsh-zotero
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
The tarball ships the built `lib
|
|
68
|
+
The tarball ships the built `lib/` (the node half plus the browser half `lib/client.js`); no local build is needed. The browser half is the configuration card: dsh web scans the package's `dsh.client` manifest and mounts it automatically, with no extra setup.
|
|
71
69
|
|
|
72
70
|
### From a local tarball
|
|
73
71
|
|
|
@@ -100,39 +98,63 @@ The plugin mounts as id `zotero` and takes effect on the next dsh start. After i
|
|
|
100
98
|
|
|
101
99
|
All values are `Config` fields changeable from the bundle's `config` block (e.g. via `dsh plugin config`). Defaults are shown.
|
|
102
100
|
|
|
103
|
-
| Field | Default | Meaning
|
|
104
|
-
| ---------------------- | ---------------------------- |
|
|
105
|
-
| `baseUrl` | `http://127.0.0.1:23119/api` | Local API base URL. Plain loopback HTTP only.
|
|
106
|
-
| `provider` | `local` | Provider id to select.
|
|
107
|
-
| `timeoutMs` | `5000` | Per-request provider deadline.
|
|
108
|
-
| `maxSearchResults` | `20` | Upper bound for `zotero_search` `limit`.
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
101
|
+
| Field | Default | Meaning |
|
|
102
|
+
| ---------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `baseUrl` | `http://127.0.0.1:23119/api` | Local API base URL. Plain loopback HTTP only. |
|
|
104
|
+
| `provider` | `local` | Provider id to select. |
|
|
105
|
+
| `timeoutMs` | `5000` | Per-request provider deadline. |
|
|
106
|
+
| `maxSearchResults` | `20` | Upper bound for `zotero_search` `limit`. |
|
|
107
|
+
| `maxNoteScanRecords` | `200` | Upper bound for note records scanned for body matches by `zotero_search`. |
|
|
108
|
+
| `maxEvidenceChars` | `6000` | Total character budget for retrieved evidence. |
|
|
109
|
+
| `maxEvidencePassages` | `4` | Upper bound for evidence passage counts. |
|
|
110
|
+
| `maxDetailChars` | `3000` | Character budget for `zotero_get` abstract previews. |
|
|
111
|
+
| `maxNoteBodyChars` | `30000` | Character budget for a note item's own body returned by `zotero_get`. |
|
|
112
|
+
| `maxNoteChars` | `2000` | Character budget per note preview in `zotero_get`. |
|
|
113
|
+
| `maxNoteRecords` | `50` | Upper bound for note records returned by `zotero_get`. |
|
|
114
|
+
| `maxAnnotationRecords` | `100` | Upper bound for annotation records returned by `zotero_get`. |
|
|
115
|
+
| `fulltextChunkWords` | `200` | Word count per full-text passage entering evidence ranking. |
|
|
116
|
+
| `maxFulltextChars` | `250000` | Full text accepted into evidence ranking. |
|
|
117
|
+
| `maxResponseBytes` | `16777216` | Streaming byte bound for every API response. |
|
|
118
|
+
| `maxExportChars` | `1000000` | Export output hard limit. Never mid-truncated. |
|
|
119
|
+
| `maxExportRefs` | `1000` | Upper bound for refs in one `zotero_export` call; keeps the request line under the server's HTTP header limit. |
|
|
120
|
+
| `defaultStyle` | `apa` | CSL style for citation/bibliography formats. |
|
|
121
|
+
| `defaultLocale` | `en-US` | CSL locale for citation/bibliography formats. |
|
|
122
|
+
|
|
123
|
+
### Web configuration
|
|
124
|
+
|
|
125
|
+
The plugin registers a "Zotero" card in dsh web's **Settings → Plugins → Plugin configuration** page listing all 19 fields above. The card binds the `zotero` settings namespace: writes land in the `zotero:` section of `$DSH_HOME/settings.yaml` (layered over the patch entry's `config`, user layer wins), and **saves apply live** — the transport and the provider rebuild on the new values, so the next tool call or `/zotero status` uses them without a dsh restart.
|
|
126
|
+
|
|
127
|
+
- Invalid values (a non-loopback `baseUrl`, a non-positive limit) are refused before the write; the card reports the failed save and keeps the draft, and the plugin keeps running on the last valid value.
|
|
128
|
+
- Every field shows its effective value; fields overridden by the settings document carry an "Overridden" badge and offer a one-click reset (clears the user layer, back to the patch entry value).
|
|
129
|
+
- External edits to the settings document (e.g. editing `settings.yaml` directly) hot-apply too.
|
|
130
|
+
- Compositions without a settings service (pure headless) never register the namespace, and the plugin behaves exactly as if unconfigured.
|
|
131
|
+
|
|
132
|
+
## Limits
|
|
133
|
+
|
|
134
|
+
- Read-only library: no path modifies items, notes, tags, or collections.
|
|
135
|
+
- Full-text evidence depends on Zotero's index: `everything` search and `retrieve` full-text passages both require indexing.
|
|
136
|
+
- Note-content search is a client-side scan: library/collection scopes and the first result page only, bounded by `maxNoteScanRecords`; notes beyond the cap never match.
|
|
137
|
+
- Attachment depth depends on the harness composition: `zotero_attachment` returns the file location; reading that PDF further needs a matching file/PDF capability.
|
|
138
|
+
- Evidence ranking is term-based relevance, not embedding or semantic search.
|
|
121
139
|
|
|
122
140
|
## Development
|
|
123
141
|
|
|
124
142
|
### Commands
|
|
125
143
|
|
|
126
144
|
```sh
|
|
127
|
-
npm install # uses a local npm cache
|
|
128
|
-
npm test # unit tests (mock Zotero server)
|
|
145
|
+
npm install # uses a local npm cache (see the workspace note below)
|
|
146
|
+
npm test # unit tests (mock Zotero server + browser card tests)
|
|
129
147
|
npm run test:coverage # 100% coverage gate on src/
|
|
130
|
-
npm run typecheck # tsc --noEmit,
|
|
131
|
-
npm run build # emits lib/
|
|
148
|
+
npm run typecheck # tsc --noEmit, node / test / client projects
|
|
149
|
+
npm run build # tsc emits the node half into lib/; esbuild emits the browser half lib/client.js
|
|
150
|
+
npm run build:client # rebuild the browser half only (self-checks the loader handoff)
|
|
151
|
+
npm run dev:client # watch the browser half (pair with the hot-swap overlay)
|
|
132
152
|
npm run format # prettier --write across the repo
|
|
133
153
|
npm run format:check # verify formatting (run before committing)
|
|
134
154
|
```
|
|
135
155
|
|
|
156
|
+
> This checkout sits inside the deepseek-harness workspace tree: the parent `package.json` declares `workspaces`, so npm walks up to it and tries to install the whole workspace. Run `npm install --no-workspaces` instead (or drop a `.npmrc` with `workspaces=false` in this repository).
|
|
157
|
+
|
|
136
158
|
Integration tests run against a live Zotero and stay skipped unless enabled:
|
|
137
159
|
|
|
138
160
|
```sh
|
|
@@ -175,6 +197,8 @@ dsh web --patch ./dev-lib.cordis.yml --port 3307
|
|
|
175
197
|
|
|
176
198
|
Hot swap affects only the instance started with `--patch`; the resident instance keeps running the tarball version.
|
|
177
199
|
|
|
200
|
+
**Developing the browser half**: the web frontend only scans loader rows whose `name` is a bare package name (resolvable to `package.json`) — the absolute-path row in `dev-lib.cordis.yml` has no browser half, so the card does not appear in the dev instance. To develop the card, install this checkout into the profile (`npm install <this repo path>` as a file: dependency, or pack and install the tarball), then pair `npm run dev:client` (esbuild watch) with the hot-swap overlay: browser-bundle changes make HMR re-fetch `/plugins/dsh-zotero/client.js`.
|
|
201
|
+
|
|
178
202
|
## License
|
|
179
203
|
|
|
180
204
|
MIT. See [LICENSE](./LICENSE).
|
package/README.md
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">dsh-zotero</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="README.en.md"><b>English</b></a> · <b>中文</b>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome DSH Plugin"></a>
|
|
9
|
+
<img src="https://img.shields.io/npm/v/dsh-zotero" alt="npm version">
|
|
10
|
+
<img src="https://img.shields.io/npm/dm/dsh-zotero" alt="npm downloads">
|
|
11
|
+
<img src="https://img.shields.io/npm/l/dsh-zotero" alt="license">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
7
14
|
让 Agents 搜索、阅读并引用你的本地 [Zotero](https://www.zotero.org) 文献库:找文献、查看笔记与批注、按问题取证、打开原文、生成引用。
|
|
8
15
|
|
|
9
16
|
在会话里用自然语言描述需求,Agent 自动按需调用下面的工具;唯一的手动命令是 `/zotero status`。
|
|
10
17
|
|
|
11
18
|
## 工具
|
|
12
19
|
|
|
13
|
-
| 工具 | 用途
|
|
14
|
-
| ------------------- |
|
|
15
|
-
| `zotero_search` | 发现:按标题/作者/年份搜索库里的资料,`everything`
|
|
16
|
-
| `zotero_get` | 检查:读取一条资料的结构化核心元数据,可选检查笔记、注释、附件的清单与预览。
|
|
17
|
-
| `zotero_retrieve` |
|
|
18
|
-
| `zotero_attachment` | 原文:解析条目或附件 ref,返回原始附件已验证的磁盘路径或链接 URL
|
|
19
|
-
| `zotero_export` | 引用:让 Zotero 按自己的 citation/export 能力生成结果(引用、CSL 参考文献表、`bibtex` / `biblatex` / `ris` / `csljson`)。
|
|
20
|
-
|
|
21
|
-
每个工具都返回形如 `zotero://user/0/<item|attachment|annotation|collection|search>/<KEY>` 的可复用 ref,后续操作都通过它串联。Zotero 10+ 的 `?server=<id>` 限定符把 ref 绑定到产生它的数据库身份,数据库切换时阻止误读旧 ref。
|
|
20
|
+
| 工具 | 用途 |
|
|
21
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `zotero_search` | 发现:按标题/作者/年份搜索库里的资料,`everything` 模式连全文索引一起搜;可限定某个分类或已保存搜索 |
|
|
23
|
+
| `zotero_get` | 检查:读取一条资料的结构化核心元数据,可选检查笔记、注释、附件的清单与预览。 |
|
|
24
|
+
| `zotero_retrieve` | 取证:按问题返回最相关的有界证据片段(注释、笔记、摘要、全文分块) |
|
|
25
|
+
| `zotero_attachment` | 原文:解析条目或附件 ref,返回原始附件已验证的磁盘路径或链接 URL |
|
|
26
|
+
| `zotero_export` | 引用:让 Zotero 按自己的 citation/export 能力生成结果(引用、CSL 参考文献表、`bibtex` / `biblatex` / `ris` / `csljson`)。 |
|
|
22
27
|
|
|
23
28
|
## 使用示例
|
|
24
29
|
|
|
@@ -46,13 +51,6 @@ Agent 按需求逐层深入,一段典型对话:
|
|
|
46
51
|
|
|
47
52
|
`/zotero status` 报告连通性、API/schema 版本和数据库身份标识(Server ID,Zotero 10+)。这是唯一的健康检查。普通调用失败时返回带类型的领域错误。
|
|
48
53
|
|
|
49
|
-
## 限制
|
|
50
|
-
|
|
51
|
-
- 对文献库只读:V1 没有任何修改条目、笔记、标签、分类等文献库数据的路径。
|
|
52
|
-
- 全文证据依赖 Zotero 的全文索引:`everything` 搜索和 `retrieve` 的全文片段都以索引为前提。
|
|
53
|
-
- 附件深度分析取决于当前 Harness 配置:`zotero_attachment` 返回文件位置,能否继续读取该 PDF 由 composition 里是否有相应文件/PDF 能力决定。
|
|
54
|
-
- 证据排序是词项相关度检索,不是 embedding 或语义搜索。
|
|
55
|
-
|
|
56
54
|
## 环境要求
|
|
57
55
|
|
|
58
56
|
- 已安装 Zotero 桌面版,并启用本地 API:**设置 → 高级 → “Allow other applications on this computer to communicate with Zotero”**。
|
|
@@ -67,7 +65,7 @@ Agent 按需求逐层深入,一段典型对话:
|
|
|
67
65
|
dsh plugin --profile <name> add dsh-zotero
|
|
68
66
|
```
|
|
69
67
|
|
|
70
|
-
tarball 内含已构建的 `lib
|
|
68
|
+
tarball 内含已构建的 `lib/`(node 半与浏览器半 `lib/client.js`),无需本地构建。浏览器半边是配置卡片:dsh web 会扫描到包内声明的 `dsh.client` 清单并自动挂载,无需额外配置。
|
|
71
69
|
|
|
72
70
|
### 本地 tarball
|
|
73
71
|
|
|
@@ -100,39 +98,63 @@ allowBuilds:
|
|
|
100
98
|
|
|
101
99
|
所有值都是 `Config` 字段,可在 bundle 的 `config` 块中修改(例如通过 `dsh plugin config`)。以下为默认值。
|
|
102
100
|
|
|
103
|
-
| 字段 | 默认值 | 含义
|
|
104
|
-
| ---------------------- | ---------------------------- |
|
|
105
|
-
| `baseUrl` | `http://127.0.0.1:23119/api` | 本地 API 基础 URL。仅支持纯回环 HTTP。
|
|
106
|
-
| `provider` | `local` | 要选择的 provider id。
|
|
107
|
-
| `timeoutMs` | `5000` | 每个请求的 provider 超时时间。
|
|
108
|
-
| `maxSearchResults` | `20` | `zotero_search` `limit` 的上限。
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
101
|
+
| 字段 | 默认值 | 含义 |
|
|
102
|
+
| ---------------------- | ---------------------------- | ------------------------------------------------------------------------- |
|
|
103
|
+
| `baseUrl` | `http://127.0.0.1:23119/api` | 本地 API 基础 URL。仅支持纯回环 HTTP。 |
|
|
104
|
+
| `provider` | `local` | 要选择的 provider id。 |
|
|
105
|
+
| `timeoutMs` | `5000` | 每个请求的 provider 超时时间。 |
|
|
106
|
+
| `maxSearchResults` | `20` | `zotero_search` `limit` 的上限。 |
|
|
107
|
+
| `maxNoteScanRecords` | `200` | `zotero_search` 补扫笔记正文的笔记数量上限。 |
|
|
108
|
+
| `maxEvidenceChars` | `6000` | 检索证据的总字符预算。 |
|
|
109
|
+
| `maxEvidencePassages` | `4` | 证据片段数量的上限。 |
|
|
110
|
+
| `maxDetailChars` | `3000` | `zotero_get` 摘要预览的字符预算。 |
|
|
111
|
+
| `maxNoteBodyChars` | `30000` | `zotero_get` 返回 note 条目自身正文的字符预算。 |
|
|
112
|
+
| `maxNoteChars` | `2000` | `zotero_get` 单条笔记预览的字符预算。 |
|
|
113
|
+
| `maxNoteRecords` | `50` | `zotero_get` 返回笔记数量的上限。 |
|
|
114
|
+
| `maxAnnotationRecords` | `100` | `zotero_get` 返回批注数量的上限。 |
|
|
115
|
+
| `fulltextChunkWords` | `200` | 进入证据排序的全文片段词数。 |
|
|
116
|
+
| `maxFulltextChars` | `250000` | 进入证据排序的全文大小上限。 |
|
|
117
|
+
| `maxResponseBytes` | `16777216` | 每个 API 响应的流式字节上限。 |
|
|
118
|
+
| `maxExportChars` | `1000000` | 导出输出的硬上限。不会中途截断。 |
|
|
119
|
+
| `maxExportRefs` | `1000` | 单次 `zotero_export` 的 refs 数量上限,保护请求行不超服务器 HTTP 头限制。 |
|
|
120
|
+
| `defaultStyle` | `apa` | 引用/参考文献使用的 CSL 样式。 |
|
|
121
|
+
| `defaultLocale` | `en-US` | 引用/参考文献使用的 CSL locale。 |
|
|
122
|
+
|
|
123
|
+
### Web 配置
|
|
124
|
+
|
|
125
|
+
插件在 dsh web 的 **设置 → 插件 → 插件配置** 页注册了一张 "Zotero" 卡片,列出上表全部 19 个字段。卡片绑定 `zotero` 设置命名空间:写入的内容落在 `$DSH_HOME/settings.yaml` 的 `zotero:` 段(与补丁 entry 的 `config` 叠层,用户段优先),**保存即热生效**——传输层与 provider 会按新值重建,下一个工具调用或 `/zotero status` 立即使用新配置,无需重启 dsh。
|
|
126
|
+
|
|
127
|
+
- 非法值(如非回环的 `baseUrl`、非正整数的限制)在写入前被拒绝,卡片提示保存失败并保留草稿,插件继续运行于上一个合法值。
|
|
128
|
+
- 每个字段显示当前生效值;被设置文档覆盖的字段带「已覆盖」标记,可一键恢复默认(清除用户段,回到补丁 entry 值)。
|
|
129
|
+
- 设置文档被外部编辑(如直接改 `settings.yaml`)时同样会热生效。
|
|
130
|
+
- 无 settings 服务的组合(纯 headless)不注册命名空间,插件行为与未配置时完全一致。
|
|
131
|
+
|
|
132
|
+
## 限制
|
|
133
|
+
|
|
134
|
+
- 对文献库只读:没有任何修改条目、笔记、标签、分类等文献库数据的路径。
|
|
135
|
+
- 全文证据依赖 Zotero 的全文索引:`everything` 搜索和 `retrieve` 的全文片段都以索引为前提。
|
|
136
|
+
- 笔记正文搜索是插件侧补扫:仅库/分类范围、仅结果首页、受 `maxNoteScanRecords` 上限约束,超出上限的笔记不参与匹配。
|
|
137
|
+
- 附件深度分析取决于当前 Harness 配置:`zotero_attachment` 返回文件位置,能否继续读取该 PDF 由 composition 里是否有相应文件/PDF 能力决定。
|
|
138
|
+
- 证据排序是词项相关度检索,不是 embedding 或语义搜索。
|
|
121
139
|
|
|
122
140
|
## 开发
|
|
123
141
|
|
|
124
142
|
### 命令
|
|
125
143
|
|
|
126
144
|
```sh
|
|
127
|
-
npm install # 使用本地 npm
|
|
128
|
-
npm test # 单元测试(mock Zotero server
|
|
145
|
+
npm install # 使用本地 npm 缓存(见下方 workspace 说明)
|
|
146
|
+
npm test # 单元测试(mock Zotero server + 浏览器卡片测试)
|
|
129
147
|
npm run test:coverage # 对 src/ 的 100% 覆盖率门禁
|
|
130
|
-
npm run typecheck # tsc --noEmit,
|
|
131
|
-
npm run build # 生成 lib/
|
|
148
|
+
npm run typecheck # tsc --noEmit,node / test / client 三个项目
|
|
149
|
+
npm run build # tsc 生成 node 半 lib/ + esbuild 生成浏览器半 lib/client.js
|
|
150
|
+
npm run build:client # 只重建浏览器半(含 loader 交接格式自检)
|
|
151
|
+
npm run dev:client # 浏览器半 watch 模式(配合热替换 overlay)
|
|
132
152
|
npm run format # prettier --write 全仓格式化
|
|
133
153
|
npm run format:check # 校验格式化(提交前执行)
|
|
134
154
|
```
|
|
135
155
|
|
|
156
|
+
> 本仓库位于 deepseek-harness workspace 树内:父目录 `package.json` 声明了 `workspaces`,npm 会向上找到它并尝试安装整个 workspace。请使用 `npm install --no-workspaces`(或在本仓库放置含 `workspaces=false` 的 `.npmrc`)。
|
|
157
|
+
|
|
136
158
|
集成测试面向真实 Zotero,默认跳过,需显式开启:
|
|
137
159
|
|
|
138
160
|
```sh
|
|
@@ -175,6 +197,8 @@ dsh web --patch ./dev-lib.cordis.yml --port 3307
|
|
|
175
197
|
|
|
176
198
|
热替换仅作用于通过 `--patch` 启动的实例;常驻实例继续运行 tarball 版本。
|
|
177
199
|
|
|
200
|
+
**浏览器半边的开发**:Web 端只扫描 Loader 行 `name` 为裸包名(npm 可解析到 `package.json`)的条目——`dev-lib.cordis.yml` 的绝对路径行不会加载浏览器半边,因此卡片不会出现在 dev 实例中。开发卡片时把本仓库装进 profile(`npm install <本仓库路径>` 作为 file: 依赖,或 `npm pack` 后安装 tarball),再配合 `npm run dev:client`(esbuild watch)与热替换 overlay:浏览器 bundle 变化会触发 HMR 重新拉取 `/plugins/dsh-zotero/client.js`。
|
|
201
|
+
|
|
178
202
|
## 许可证
|
|
179
203
|
|
|
180
204
|
本插件以 [MIT](./LICENSE) 许可证发布。
|
package/lib/ask.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive recovery for Zotero connectivity failures.
|
|
3
|
+
*
|
|
4
|
+
* When a request-driven tool call fails because Zotero cannot serve it
|
|
5
|
+
* (not running, local API disabled, unsupported API version, timeout),
|
|
6
|
+
* the caller is asked how to proceed through the `userQuestions` seam,
|
|
7
|
+
* with the recommended action offered first. The ask happens only inside
|
|
8
|
+
* a tool call that actually attempted a Zotero request — loading the
|
|
9
|
+
* plugin never probes, and a tool that was never called never asks.
|
|
10
|
+
* Everything here fails closed: an absent question service, a failed
|
|
11
|
+
* question, or a non-retry answer all surface the original typed
|
|
12
|
+
* `ZoteroError`, so a broken question mechanism can never mask a broken
|
|
13
|
+
* Zotero connection, and a retry is attempted at most once.
|
|
14
|
+
* @module dsh-zotero/ask
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
import { type ToolRunContext } from '@deepseek-ai/dsh-tools';
|
|
18
|
+
/** The connectivity failure codes that warrant asking the user how to proceed. */
|
|
19
|
+
export declare const ASK_WORTHY_CODES: readonly ["ZOTERO_NOT_RUNNING", "ZOTERO_API_DISABLED", "ZOTERO_API_VERSION", "ZOTERO_TIMEOUT"];
|
|
20
|
+
export type AskWorthyCode = (typeof ASK_WORTHY_CODES)[number];
|
|
21
|
+
/** The parts of a tool execution `withConnectivityAsk` needs. */
|
|
22
|
+
export type ConnectivityAskExec = Pick<ToolRunContext, 'signal' | 'agent'>;
|
|
23
|
+
/**
|
|
24
|
+
* Run one Zotero request; on a connectivity failure, ask the user how to
|
|
25
|
+
* proceed and retry at most once when they choose the recommended action.
|
|
26
|
+
* @param ctx - the plugin context; the question service is looked up
|
|
27
|
+
* optionally, so headless compositions skip the ask.
|
|
28
|
+
* @param exec - the tool execution (signal and agent) the failure belongs to.
|
|
29
|
+
* @param run - the request to attempt; must be re-runnable with identical
|
|
30
|
+
* arguments, because the retry path calls it a second time.
|
|
31
|
+
* @returns the request result, or throws the original `ZoteroError` when
|
|
32
|
+
* the failure is not ask-worthy, no question service exists, the user
|
|
33
|
+
* does not choose to retry, the question itself fails, or the retry
|
|
34
|
+
* fails again.
|
|
35
|
+
*/
|
|
36
|
+
export declare function withConnectivityAsk<T>(ctx: Context, exec: ConnectivityAskExec, run: () => Promise<T>): Promise<T>;
|
|
37
|
+
//# sourceMappingURL=ask.d.ts.map
|
package/lib/ask.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAe1E,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,gGAKnB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D,iEAAiE;AACjE,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAA;AA2E1E;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CA4BZ"}
|
package/lib/ask.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive recovery for Zotero connectivity failures.
|
|
3
|
+
*
|
|
4
|
+
* When a request-driven tool call fails because Zotero cannot serve it
|
|
5
|
+
* (not running, local API disabled, unsupported API version, timeout),
|
|
6
|
+
* the caller is asked how to proceed through the `userQuestions` seam,
|
|
7
|
+
* with the recommended action offered first. The ask happens only inside
|
|
8
|
+
* a tool call that actually attempted a Zotero request — loading the
|
|
9
|
+
* plugin never probes, and a tool that was never called never asks.
|
|
10
|
+
* Everything here fails closed: an absent question service, a failed
|
|
11
|
+
* question, or a non-retry answer all surface the original typed
|
|
12
|
+
* `ZoteroError`, so a broken question mechanism can never mask a broken
|
|
13
|
+
* Zotero connection, and a retry is attempted at most once.
|
|
14
|
+
* @module dsh-zotero/ask
|
|
15
|
+
*/
|
|
16
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
17
|
+
import { TOOL_ABORTED } from '@deepseek-ai/dsh-tools';
|
|
18
|
+
import { ZOTERO_API_DISABLED, ZOTERO_API_VERSION, ZOTERO_NOT_RUNNING, ZOTERO_TIMEOUT, ZoteroError, } from './errors.js';
|
|
19
|
+
/** The connectivity failure codes that warrant asking the user how to proceed. */
|
|
20
|
+
export const ASK_WORTHY_CODES = [
|
|
21
|
+
ZOTERO_NOT_RUNNING,
|
|
22
|
+
ZOTERO_API_DISABLED,
|
|
23
|
+
ZOTERO_API_VERSION,
|
|
24
|
+
ZOTERO_TIMEOUT,
|
|
25
|
+
];
|
|
26
|
+
const ABORT_LABEL = 'Abort this query';
|
|
27
|
+
const ABORT_DESCRIPTION = 'Stop this operation; ask me to retry later if you still need it.';
|
|
28
|
+
const RETRY_DESCRIPTION = 'Re-run the query with the original parameters.';
|
|
29
|
+
const FAILURE_SPECS = {
|
|
30
|
+
[ZOTERO_NOT_RUNNING]: {
|
|
31
|
+
header: 'Zotero is not running',
|
|
32
|
+
question: 'Zotero is not running, so I cannot read your library. What should I do?',
|
|
33
|
+
detail: 'Start Zotero, then in Settings → Advanced check "Allow other applications on this computer to communicate with Zotero".',
|
|
34
|
+
retryLabel: 'I started Zotero, retry (Recommended)',
|
|
35
|
+
retryDescription: RETRY_DESCRIPTION,
|
|
36
|
+
abortLabel: ABORT_LABEL,
|
|
37
|
+
abortDescription: ABORT_DESCRIPTION,
|
|
38
|
+
},
|
|
39
|
+
[ZOTERO_API_DISABLED]: {
|
|
40
|
+
header: 'Zotero local API is disabled',
|
|
41
|
+
question: 'Zotero is running but rejected the local API request (403).',
|
|
42
|
+
detail: 'In Zotero Settings → Advanced, check "Allow other applications on this computer to communicate with Zotero".',
|
|
43
|
+
retryLabel: 'I enabled the local API, retry (Recommended)',
|
|
44
|
+
retryDescription: RETRY_DESCRIPTION,
|
|
45
|
+
abortLabel: ABORT_LABEL,
|
|
46
|
+
abortDescription: ABORT_DESCRIPTION,
|
|
47
|
+
},
|
|
48
|
+
[ZOTERO_API_VERSION]: {
|
|
49
|
+
header: 'Zotero version too old',
|
|
50
|
+
question: 'The running Zotero does not speak local API version 3, which this plugin requires.',
|
|
51
|
+
detail: 'Upgrade Zotero to a version whose local API supports version 3.',
|
|
52
|
+
retryLabel: 'I upgraded Zotero, retry (Recommended)',
|
|
53
|
+
retryDescription: RETRY_DESCRIPTION,
|
|
54
|
+
abortLabel: ABORT_LABEL,
|
|
55
|
+
abortDescription: ABORT_DESCRIPTION,
|
|
56
|
+
},
|
|
57
|
+
[ZOTERO_TIMEOUT]: {
|
|
58
|
+
header: 'Zotero timed out',
|
|
59
|
+
question: 'Zotero did not respond within the timeout (it may be indexing a large library).',
|
|
60
|
+
detail: 'The request failed after the configured timeout.',
|
|
61
|
+
retryLabel: 'Retry (Recommended)',
|
|
62
|
+
retryDescription: 'Run the same request again.',
|
|
63
|
+
abortLabel: ABORT_LABEL,
|
|
64
|
+
abortDescription: ABORT_DESCRIPTION,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
function isAskWorthyCode(code) {
|
|
68
|
+
return ASK_WORTHY_CODES.includes(code);
|
|
69
|
+
}
|
|
70
|
+
function questionOf(spec) {
|
|
71
|
+
return {
|
|
72
|
+
id: 'zotero-failure',
|
|
73
|
+
question: spec.question,
|
|
74
|
+
header: spec.header,
|
|
75
|
+
detail: spec.detail,
|
|
76
|
+
options: [
|
|
77
|
+
{ label: spec.retryLabel, description: spec.retryDescription },
|
|
78
|
+
{ label: spec.abortLabel, description: spec.abortDescription },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Run one Zotero request; on a connectivity failure, ask the user how to
|
|
84
|
+
* proceed and retry at most once when they choose the recommended action.
|
|
85
|
+
* @param ctx - the plugin context; the question service is looked up
|
|
86
|
+
* optionally, so headless compositions skip the ask.
|
|
87
|
+
* @param exec - the tool execution (signal and agent) the failure belongs to.
|
|
88
|
+
* @param run - the request to attempt; must be re-runnable with identical
|
|
89
|
+
* arguments, because the retry path calls it a second time.
|
|
90
|
+
* @returns the request result, or throws the original `ZoteroError` when
|
|
91
|
+
* the failure is not ask-worthy, no question service exists, the user
|
|
92
|
+
* does not choose to retry, the question itself fails, or the retry
|
|
93
|
+
* fails again.
|
|
94
|
+
*/
|
|
95
|
+
export async function withConnectivityAsk(ctx, exec, run) {
|
|
96
|
+
try {
|
|
97
|
+
return await run();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (!(error instanceof ZoteroError) || !isAskWorthyCode(error.code))
|
|
101
|
+
throw error;
|
|
102
|
+
const questions = ctx.get('userQuestions');
|
|
103
|
+
if (questions === undefined)
|
|
104
|
+
throw error;
|
|
105
|
+
const spec = FAILURE_SPECS[error.code];
|
|
106
|
+
let answer;
|
|
107
|
+
try {
|
|
108
|
+
const request = {
|
|
109
|
+
questions: [questionOf(spec)],
|
|
110
|
+
...(exec.agent !== undefined ? { agent: exec.agent } : {}),
|
|
111
|
+
signal: exec.signal,
|
|
112
|
+
};
|
|
113
|
+
answer = await questions.ask(request);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// A failed question (no provider, aborted ask, delegated caller) must
|
|
117
|
+
// never mask the underlying connectivity failure.
|
|
118
|
+
if (exec.signal?.aborted)
|
|
119
|
+
throw new HarnessError('tool call aborted', TOOL_ABORTED);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
const answerItem = answer.answers.find((item) => item.id === 'zotero-failure');
|
|
123
|
+
const selected = answerItem?.selected ?? [];
|
|
124
|
+
if (!selected.includes(spec.retryLabel))
|
|
125
|
+
throw error;
|
|
126
|
+
// Outside the catch: a second failure propagates as-is, never re-asking.
|
|
127
|
+
return await run();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=ask.js.map
|
package/lib/ask.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask.js","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"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"}
|
package/lib/attachments.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,GACZ;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAKlD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,yBAAyB,CAmBlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,IAAI,EAAE,MAAM,GACX,yBAAyB,GAAG,SAAS,CAoBvC"}
|
package/lib/attachments.js
CHANGED
|
@@ -9,15 +9,7 @@
|
|
|
9
9
|
* @module dsh-zotero/attachments
|
|
10
10
|
*/
|
|
11
11
|
import { ZOTERO_UNEXPECTED, ZoteroError } from './errors.js';
|
|
12
|
-
|
|
13
|
-
function asRecord(value) {
|
|
14
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
15
|
-
? value
|
|
16
|
-
: undefined;
|
|
17
|
-
}
|
|
18
|
-
function asString(value) {
|
|
19
|
-
return typeof value === 'string' ? value : undefined;
|
|
20
|
-
}
|
|
12
|
+
import { asRecord, asString, isObjectKey } from './json.js';
|
|
21
13
|
/** Extract a Zotero object key from an API `links.attachment.href`. */
|
|
22
14
|
export function extractAttachmentKey(href) {
|
|
23
15
|
if (href === undefined)
|
|
@@ -43,7 +35,7 @@ export function bestAttachmentFromLinks(json) {
|
|
|
43
35
|
export function normalizeAttachmentRecord(json) {
|
|
44
36
|
const record = asRecord(json);
|
|
45
37
|
const key = asString(record?.key);
|
|
46
|
-
if (key === undefined || !
|
|
38
|
+
if (key === undefined || !isObjectKey(key)) {
|
|
47
39
|
throw new ZoteroError('Zotero returned an attachment without a valid object key.', ZOTERO_UNEXPECTED);
|
|
48
40
|
}
|
|
49
41
|
const data = asRecord(record?.data);
|
package/lib/attachments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
|