dsh-zotero 0.8.1 → 0.8.3

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.
Files changed (94) hide show
  1. package/README.en.md +16 -13
  2. package/README.md +16 -13
  3. package/lib/ask.d.ts +27 -1
  4. package/lib/ask.d.ts.map +1 -1
  5. package/lib/ask.js +61 -19
  6. package/lib/ask.js.map +1 -1
  7. package/lib/client.js +281 -302
  8. package/lib/client.js.map +4 -4
  9. package/lib/command.d.ts.map +1 -1
  10. package/lib/command.js +6 -1
  11. package/lib/command.js.map +1 -1
  12. package/lib/concurrency.d.ts +40 -1
  13. package/lib/concurrency.d.ts.map +1 -1
  14. package/lib/concurrency.js +101 -1
  15. package/lib/concurrency.js.map +1 -1
  16. package/lib/config.d.ts +1 -1
  17. package/lib/config.d.ts.map +1 -1
  18. package/lib/constants.d.ts +35 -0
  19. package/lib/constants.d.ts.map +1 -1
  20. package/lib/constants.js +35 -0
  21. package/lib/constants.js.map +1 -1
  22. package/lib/contract.d.ts +2 -0
  23. package/lib/contract.d.ts.map +1 -1
  24. package/lib/contract.js +1 -0
  25. package/lib/contract.js.map +1 -1
  26. package/lib/errors.d.ts +13 -1
  27. package/lib/errors.d.ts.map +1 -1
  28. package/lib/errors.js +17 -0
  29. package/lib/errors.js.map +1 -1
  30. package/lib/evidence.d.ts +9 -4
  31. package/lib/evidence.d.ts.map +1 -1
  32. package/lib/evidence.js +13 -6
  33. package/lib/evidence.js.map +1 -1
  34. package/lib/http-client.d.ts +34 -0
  35. package/lib/http-client.d.ts.map +1 -1
  36. package/lib/http-client.js +132 -13
  37. package/lib/http-client.js.map +1 -1
  38. package/lib/local/changes-domain.d.ts +37 -10
  39. package/lib/local/changes-domain.d.ts.map +1 -1
  40. package/lib/local/changes-domain.js +385 -114
  41. package/lib/local/changes-domain.js.map +1 -1
  42. package/lib/local/limits.d.ts +1 -1
  43. package/lib/local/limits.d.ts.map +1 -1
  44. package/lib/local/provider.d.ts.map +1 -1
  45. package/lib/local/provider.js +2 -1
  46. package/lib/local/provider.js.map +1 -1
  47. package/lib/local/retrieve.d.ts +9 -2
  48. package/lib/local/retrieve.d.ts.map +1 -1
  49. package/lib/local/retrieve.js +184 -41
  50. package/lib/local/retrieve.js.map +1 -1
  51. package/lib/local/search-domain.d.ts.map +1 -1
  52. package/lib/local/search-domain.js +25 -26
  53. package/lib/local/search-domain.js.map +1 -1
  54. package/lib/prompt.js +1 -1
  55. package/lib/prompt.js.map +1 -1
  56. package/lib/remote.d.ts.map +1 -1
  57. package/lib/remote.js +1 -0
  58. package/lib/remote.js.map +1 -1
  59. package/lib/search-text.d.ts +29 -0
  60. package/lib/search-text.d.ts.map +1 -0
  61. package/lib/search-text.js +79 -0
  62. package/lib/search-text.js.map +1 -0
  63. package/lib/service.d.ts +9 -0
  64. package/lib/service.d.ts.map +1 -1
  65. package/lib/service.js +9 -0
  66. package/lib/service.js.map +1 -1
  67. package/lib/tools/attachment.d.ts.map +1 -1
  68. package/lib/tools/attachment.js +20 -3
  69. package/lib/tools/attachment.js.map +1 -1
  70. package/lib/tools/browse.js +1 -1
  71. package/lib/tools/browse.js.map +1 -1
  72. package/lib/tools/changes.d.ts +177 -14
  73. package/lib/tools/changes.d.ts.map +1 -1
  74. package/lib/tools/changes.js +218 -62
  75. package/lib/tools/changes.js.map +1 -1
  76. package/lib/tools/children.js +1 -1
  77. package/lib/tools/children.js.map +1 -1
  78. package/lib/tools/export.js +1 -1
  79. package/lib/tools/export.js.map +1 -1
  80. package/lib/tools/get.js +1 -1
  81. package/lib/tools/get.js.map +1 -1
  82. package/lib/tools/retrieve.d.ts +57 -1
  83. package/lib/tools/retrieve.d.ts.map +1 -1
  84. package/lib/tools/retrieve.js +92 -18
  85. package/lib/tools/retrieve.js.map +1 -1
  86. package/lib/tools/search.js +1 -1
  87. package/lib/tools/search.js.map +1 -1
  88. package/lib/tools/validate.d.ts +7 -0
  89. package/lib/tools/validate.d.ts.map +1 -1
  90. package/lib/tools/validate.js +13 -0
  91. package/lib/tools/validate.js.map +1 -1
  92. package/lib/types.d.ts +191 -8
  93. package/lib/types.d.ts.map +1 -1
  94. package/package.json +125 -127
package/README.en.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/v/dsh-zotero" alt="npm version" style="max-width:100%;"></a>
11
11
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/dm/dsh-zotero" alt="npm downloads" style="max-width:100%;"></a>
12
12
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/l/dsh-zotero" alt="license" style="max-width:100%;"></a>
13
- <a href="https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.5-rc.1"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.5--rc.1-blue" alt="minimum dsh version" style="max-width:100%;"></a>
13
+ <a href="https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.5-rc.2"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.5--rc.2-blue" alt="minimum dsh version" style="max-width:100%;"></a>
14
14
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome DSH Plugin"></a>
15
15
  </p>
16
16
  </div>
@@ -61,7 +61,7 @@ dsh plugin --profile <name> add ./dsh-zotero-*.tgz
61
61
 
62
62
  After installing, start a new session so the agent picks up the Zotero tools.
63
63
 
64
- The plugin provides a settings card under **Settings → Plugins** where you can adjust the API address, concurrency limits, full-text retrieval toggle, and more. Changes take effect on save. See [Configuration](docs/configuration.md).
64
+ The plugin provides a settings page under **Settings → Zotero** — a left-nav entry beside General, Models, and Plugins — where you can adjust the API address, concurrency limits, full-text retrieval toggle, and more. Changes take effect on save. See [Configuration](docs/configuration.md).
65
65
 
66
66
  [Installation details →](docs/getting-started.md)
67
67
 
@@ -69,7 +69,7 @@ The plugin provides a settings card under **Settings → Plugins** where you can
69
69
 
70
70
  - Zotero ≥ 7 with local API enabled: **Settings → Advanced → "Allow other applications on this computer to communicate with Zotero"**
71
71
  - Node.js ≥ 22.19 (or ≥ 24)
72
- - dsh 0.1.5-rc.1 host (all `@deepseek-ai/dsh-*` peer dependencies are `^0.1.5-rc.1`, and the declared compatibility range is `engines.dsh`; only the latest pre-release line is supported until upstream stabilizes, with no backward compatibility)
72
+ - dsh 0.1.5-rc.2 host (all `@deepseek-ai/dsh-*` peer dependencies are `^0.1.5-rc.2`, and the declared compatibility range is `engines.dsh`; only the latest pre-release line is supported until upstream stabilizes, with no backward compatibility)
73
73
  - Local API at `http://127.0.0.1:23119/api`, unauthenticated, read-only
74
74
 
75
75
  ## Usage example
@@ -78,20 +78,23 @@ The agent calls tools step by step during a conversation. Each result becomes co
78
78
 
79
79
  ```text
80
80
  User: Find papers about Risk
81
- Agent → zotero_search(query: "Risk", itemType: "journalArticle")
81
+ Agent → zotero_search(query: "Risk", itemTypes: ["journalArticle"])
82
82
  5 matches; user picks the first 3
83
83
 
84
84
  User: What does the first one's abstract say?
85
- Agent → zotero_get(ref: 1, fields: ["abstractNote"])
86
- Returns the full abstract
85
+ Agent → zotero_get(ref: "zotero://user/0/item/ABCD1234")
86
+ Returns the full abstract (the standard model carries it)
87
87
 
88
88
  User: Find the methodology discussion in this paper
89
- Agent → zotero_retrieve(query: "methodology", sources: ["fulltext", "notes"])
89
+ Agent → zotero_retrieve(ref: "zotero://user/0/item/ABCD1234", query: "methodology",
90
+ sources: ["fulltext", "note"])
90
91
  Returns relevant passages with page numbers
91
92
 
92
93
  User: Export all three as BibTeX
93
- Agent → zotero_export(refs: [1,2,3], format: "bibtex")
94
- Generates BibTeX entries, ready to copy or download
94
+ Agent → zotero_export(refs: ["zotero://user/0/item/ABCD1234",
95
+ "zotero://user/0/item/EFGH5678",
96
+ "zotero://user/0/item/IJKL9012"], format: "bibtex")
97
+ Generates BibTeX entries; the UI can download them, the model reads the same text
95
98
  ```
96
99
 
97
100
  More examples in [Features](docs/features.md).
@@ -101,7 +104,7 @@ More examples in [Features](docs/features.md).
101
104
  - **Read-only library**: all operations are reads; items, notes, tags, and collections are unchanged
102
105
  - **Loopback only**: network requests go only to `127.0.0.1:23119`
103
106
  - **Evidence ranking is term-based**: BM25 ranks passages by query-term frequency match
104
- - **Exports are static text**: returned as text, ready to copy into your target document
107
+ - **Exports are static text**: the tool returns text, and that is what the model reads; the Zotero panel offers one-click copy or file download (`.bib`, `.ris`, `.json`), so nothing has to be retyped
105
108
  - **Full-text evidence depends on Zotero's index**: unindexed PDFs yield no full-text passages
106
109
  - **Attachment depth depends on the harness**: `zotero_attachment` returns the file location; reading the PDF further needs a matching host capability
107
110
 
@@ -109,7 +112,7 @@ More examples in [Features](docs/features.md).
109
112
 
110
113
  - **Network**: HTTP requests go only to `http://127.0.0.1:23119/api` (redirects are not followed); `resolveConfig` enforces a loopback address
111
114
  - **Filesystem**: read-only — `zotero_attachment` verifies attachment paths with `existsSync`; no file writes
112
- - **Persistence**: the only write comes from the settings card under Settings → Plugins, saved to the `zotero:` user layer of `$DSH_HOME/settings.yaml`
115
+ - **Persistence**: the only write comes from the Zotero settings page in the left navigation, saved to the `zotero:` user layer of `$DSH_HOME/settings.yaml`
113
116
  - **No shell / native / background tasks**: the plugin runs no shell commands, loads no native modules, and starts no daemon
114
117
  - **Restart**: after installing or removing the plugin, restart dsh and start a new session; configuration changes hot-reload on save without a restart
115
118
 
@@ -120,7 +123,7 @@ More examples in [Features](docs/features.md).
120
123
  | [Getting Started](docs/getting-started.md) | Installation, prerequisites, first verification |
121
124
  | [Features](docs/features.md) | Sources panel, chat integration, evidence, exports |
122
125
  | [Tool Reference](docs/tools.md) | Parameters, return values, error codes for all 8 tools |
123
- | [Configuration](docs/configuration.md) | 21 config fields, defaults, hot-reload |
126
+ | [Configuration](docs/configuration.md) | 22 config fields, defaults, hot-reload |
124
127
  | [Architecture](docs/architecture.md) | Data flow, layer responsibilities, design boundaries |
125
128
  | [Development](docs/development.md) | Build, test, local development |
126
129
  | [Troubleshooting](docs/troubleshooting.md) | 11 common issues with symptoms and fixes |
@@ -136,7 +139,7 @@ npm run dev # tsc --watch for host half hot reload
136
139
  npm run dev:client # esbuild --watch for browser half hot reload
137
140
  ```
138
141
 
139
- Build output splits into `lib/` (Node side) and `lib/client.js` (browser side — settings card + Zotero tab). For full plugin development with both halves, use the `dev-lib.cordis.yml` overlay. See [Development](docs/development.md) for details.
142
+ Build output splits into `lib/` (Node side) and `lib/client.js` (browser side — settings page + Zotero tab). For full plugin development with both halves, use the `dev-lib.cordis.yml` overlay. See [Development](docs/development.md) for details.
140
143
 
141
144
  ## License
142
145
 
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/v/dsh-zotero" alt="npm version" style="max-width:100%;"></a>
11
11
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/dm/dsh-zotero" alt="npm downloads" style="max-width:100%;"></a>
12
12
  <a href="https://www.npmjs.com/package/dsh-zotero"><img src="https://img.shields.io/npm/l/dsh-zotero" alt="license" style="max-width:100%;"></a>
13
- <a href="https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.5-rc.1"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.5--rc.1-blue" alt="最低 dsh 版本" style="max-width:100%;"></a>
13
+ <a href="https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.5-rc.2"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.5--rc.2-blue" alt="最低 dsh 版本" style="max-width:100%;"></a>
14
14
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome DSH Plugin"></a>
15
15
  </p>
16
16
  </div>
@@ -61,7 +61,7 @@ dsh plugin --profile <name> add ./dsh-zotero-*.tgz
61
61
 
62
62
  安装后重启新建会话,Agent 即可使用 Zotero 工具。
63
63
 
64
- 插件在 **Settings → Plugins** 中提供配置卡片,可调整 API 地址、并发限制、全文检索开关等参数,保存即生效。详见 [配置](docs/configuration.md)。
64
+ 插件在 **设置 → Zotero** 中提供一个配置页(与 General、Models、Plugins 并列的左侧导航项),可调整 API 地址、并发限制、全文检索开关等参数,保存即生效。详见 [配置](docs/configuration.md)。
65
65
 
66
66
  [安装详情 →](docs/getting-started.md)
67
67
 
@@ -69,7 +69,7 @@ dsh plugin --profile <name> add ./dsh-zotero-*.tgz
69
69
 
70
70
  - Zotero ≥ 7 桌面版,启用本地 API:**设置 → 高级 → "允许其他应用程序与 Zotero 通信"**
71
71
  - Node.js ≥ 22.19(或 ≥ 24)
72
- - 宿主 dsh 0.1.5-rc.1(`@deepseek-ai/dsh-*` peer 依赖均为 `^0.1.5-rc.1`,兼容范围另见 `engines.dsh`;上游发布稳定版之前只跟最新预发布线,不向后兼容)
72
+ - 宿主 dsh 0.1.5-rc.2(`@deepseek-ai/dsh-*` peer 依赖均为 `^0.1.5-rc.2`,兼容范围另见 `engines.dsh`;上游发布稳定版之前只跟最新预发布线,不向后兼容)
73
73
  - 本地 API 地址 `http://127.0.0.1:23119/api`,无认证,只读
74
74
 
75
75
  ## 使用示例
@@ -78,20 +78,23 @@ Agent 在对话中根据用户需求逐步调用工具,每次调用的结果
78
78
 
79
79
  ```text
80
80
  用户:帮我找 Risk 相关的论文
81
- Agent → zotero_search(query: "Risk", itemType: "journalArticle")
81
+ Agent → zotero_search(query: "Risk", itemTypes: ["journalArticle"])
82
82
  5 篇匹配结果,用户选择前 3 篇
83
83
 
84
84
  用户:第一篇的摘要说了什么?
85
- Agent → zotero_get(ref: 1, fields: ["abstractNote"])
86
- 返回摘要全文
85
+ Agent → zotero_get(ref: "zotero://user/0/item/ABCD1234")
86
+ 返回摘要全文(标准模型已含 abstract)
87
87
 
88
88
  用户:这篇里关于方法论的讨论,帮我找出来
89
- Agent → zotero_retrieve(query: "methodology", sources: ["fulltext", "notes"])
89
+ Agent → zotero_retrieve(ref: "zotero://user/0/item/ABCD1234", query: "methodology",
90
+ sources: ["fulltext", "note"])
90
91
  返回相关段落,带页码和来源
91
92
 
92
93
  用户:把这三篇导出为 BibTeX
93
- Agent → zotero_export(refs: [1,2,3], format: "bibtex")
94
- 生成 BibTeX 条目,可复制或下载
94
+ Agent → zotero_export(refs: ["zotero://user/0/item/ABCD1234",
95
+ "zotero://user/0/item/EFGH5678",
96
+ "zotero://user/0/item/IJKL9012"], format: "bibtex")
97
+ 生成 BibTeX 条目;界面可下载,模型读到同样的文本
95
98
  ```
96
99
 
97
100
  更多示例见 [功能概览](docs/features.md)。
@@ -101,7 +104,7 @@ Agent → zotero_export(refs: [1,2,3], format: "bibtex")
101
104
  - **只读文献库**:所有操作均为读取,不修改条目、笔记、标签或分类
102
105
  - **只访问本机**:网络请求仅发往 `127.0.0.1:23119`
103
106
  - **证据排序是词项相关性**:基于 BM25,按查询词与 passage 的词频匹配度排序
104
- - **导出是静态文本**:以文本形式返回,需要手动复制到目标位置
107
+ - **导出是静态文本**:工具以文本形式返回,模型读到的就是它;Zotero 面板可以一键复制或下载文件(`.bib` / `.ris` / `.json` 等),不需要手动誊抄
105
108
  - **全文证据依赖 Zotero 索引**:未索引的 PDF 无法提供全文段落
106
109
  - **附件深度取决于宿主**:`zotero_attachment` 返回文件位置,继续阅读 PDF 需要宿主具备对应能力
107
110
 
@@ -109,7 +112,7 @@ Agent → zotero_export(refs: [1,2,3], format: "bibtex")
109
112
 
110
113
  - **网络**:只向 `http://127.0.0.1:23119/api` 发起 HTTP 请求(不跟随重定向),`resolveConfig` 强制 loopback 地址
111
114
  - **文件**:只读,`zotero_attachment` 用 `existsSync` 校验 Zotero 返回的附件路径,不写文件系统
112
- - **持久化**:唯一写入来自 Settings → Plugins 中的配置卡片,保存到 `$DSH_HOME/settings.yaml` 的 `zotero:` 用户层
115
+ - **持久化**:唯一写入来自设置页左侧导航的 Zotero 配置页,保存到 `$DSH_HOME/settings.yaml` 的 `zotero:` 用户层
113
116
  - **无 Shell / native / 后台任务**:插件不执行 shell 命令、不加载 native 模块、不启动常驻进程
114
117
  - **重启**:安装或卸载插件后需要重启 dsh 并新建会话;配置修改保存即热更新,无需重启
115
118
 
@@ -120,7 +123,7 @@ Agent → zotero_export(refs: [1,2,3], format: "bibtex")
120
123
  | [快速上手](docs/getting-started.md) | 安装、前置条件、首次验证 |
121
124
  | [功能概览](docs/features.md) | 来源面板、对话集成、证据提取、导出 |
122
125
  | [工具参考](docs/tools.md) | 全部 8 个工具的参数、返回值、错误码 |
123
- | [配置](docs/configuration.md) | 21 个配置字段、默认值、热更新 |
126
+ | [配置](docs/configuration.md) | 22 个配置字段、默认值、热更新 |
124
127
  | [架构](docs/architecture.md) | 数据流、各层职责、设计边界 |
125
128
  | [开发指南](docs/development.md) | 构建、测试、本地开发 |
126
129
  | [问题排查](docs/troubleshooting.md) | 11 个常见问题的症状和处理 |
@@ -136,7 +139,7 @@ npm run dev # tsc --watch,host half 热更新
136
139
  npm run dev:client # esbuild --watch,浏览器部分热更新
137
140
  ```
138
141
 
139
- `lib/` 放 Node 侧代码,`lib/client.js` 放浏览器侧代码(settings 卡片和 Zotero tab)。你用 `dev-lib.cordis.yml` overlay 跑完整插件流程,见[开发指南](docs/development.md)。本仓库与 `../deepseek-harness` 并列,属本地暂存布局。
142
+ `lib/` 放 Node 侧代码,`lib/client.js` 放浏览器侧代码(设置页和 Zotero tab)。你用 `dev-lib.cordis.yml` overlay 跑完整插件流程,见[开发指南](docs/development.md)。本仓库与 `../deepseek-harness` 并列,属本地暂存布局。
140
143
 
141
144
  ## 许可证
142
145
 
package/lib/ask.d.ts CHANGED
@@ -17,11 +17,37 @@ import type { Context } from '@deepseek-ai/cordis';
17
17
  import { type ToolRunContext } from '@deepseek-ai/dsh-tools';
18
18
  /** The parts of a tool execution `withConnectivityAsk` needs. */
19
19
  type ConnectivityAskExec = Pick<ToolRunContext, 'signal' | 'agent'>;
20
+ /**
21
+ * One recovery conversation per failure kind, owned by one plugin instance.
22
+ *
23
+ * Tool calls run concurrently, and a Zotero that is down fails all of them:
24
+ * five parallel reads used to put five identical cards in front of the user,
25
+ * each one demanding the same answer. The gate keeps one ask per failure
26
+ * kind in flight and lets every other caller wait on that same answer, so a
27
+ * single card decides for all of them — and each caller then retries, or
28
+ * does not, on its own request. The entry is dropped as soon as the question
29
+ * settles, so a later failure asks again instead of inheriting a stale
30
+ * answer.
31
+ */
32
+ export declare class ConnectivityRecovery {
33
+ private readonly asking;
34
+ /**
35
+ * Answer one failure kind, asking only when no question for it is in
36
+ * flight.
37
+ * @param code - the failure kind the question belongs to.
38
+ * @param question - the ask to run when this caller is the first; resolves
39
+ * true when the user chose to retry.
40
+ * @returns the answer this caller acts on.
41
+ */
42
+ ask(code: string, question: () => Promise<boolean>): Promise<boolean>;
43
+ }
20
44
  /**
21
45
  * Run one Zotero request; on a connectivity failure, ask the user how to
22
46
  * proceed and retry at most once when they choose the recommended action.
23
47
  * @param ctx - the plugin context; the question service is looked up
24
48
  * optionally, so headless compositions skip the ask.
49
+ * @param recovery - the instance's recovery gate: callers that failed the
50
+ * same way share one question instead of stacking identical cards.
25
51
  * @param exec - the tool execution (signal and agent) the failure belongs to.
26
52
  * @param run - the request to attempt; must be re-runnable with identical
27
53
  * arguments, because the retry path calls it a second time.
@@ -30,6 +56,6 @@ type ConnectivityAskExec = Pick<ToolRunContext, 'signal' | 'agent'>;
30
56
  * does not choose to retry, the question itself fails, or the retry
31
57
  * fails again.
32
58
  */
33
- export declare function withConnectivityAsk<T>(ctx: Context, exec: ConnectivityAskExec, run: () => Promise<T>): Promise<T>;
59
+ export declare function withConnectivityAsk<T>(ctx: Context, recovery: ConnectivityRecovery, exec: ConnectivityAskExec, run: () => Promise<T>): Promise<T>;
34
60
  export {};
35
61
  //# sourceMappingURL=ask.d.ts.map
package/lib/ask.d.ts.map CHANGED
@@ -1 +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;AAyB1E,iEAAiE;AACjE,KAAK,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAA;AA2EnE;;;;;;;;;;;;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,CAgCZ"}
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;AA0B1E,iEAAiE;AACjE,KAAK,mBAAmB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAA;AA2EnE;;;;;;;;;;;GAWG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAE7D;;;;;;;OAOG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAW5E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,oBAAoB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAmCZ"}
package/lib/ask.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { HarnessError } from '@deepseek-ai/dsh-llm';
17
17
  import { TOOL_ABORTED } from '@deepseek-ai/dsh-tools';
18
+ import { ZOTERO_LOCAL_API_VERSION } from './constants.js';
18
19
  import { ZOTERO_API_DISABLED, ZOTERO_API_VERSION, ZOTERO_NOT_RUNNING, ZOTERO_TIMEOUT, ZoteroError, } from './errors.js';
19
20
  /** The connectivity failure codes that warrant asking the user how to proceed. */
20
21
  const ASK_WORTHY_CODES = [
@@ -46,10 +47,10 @@ const FAILURE_SPECS = {
46
47
  abortDescription: ABORT_DESCRIPTION,
47
48
  },
48
49
  [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)',
50
+ header: 'Zotero and this plugin share no API version',
51
+ question: `The running Zotero does not implement local API version ${ZOTERO_LOCAL_API_VERSION}, which this plugin requires.`,
52
+ detail: `Upgrade Zotero to a version whose local API supports version ${ZOTERO_LOCAL_API_VERSION} — or update dsh-zotero if the running Zotero is newer than this plugin line.`,
53
+ retryLabel: 'I fixed the version, retry (Recommended)',
53
54
  retryDescription: RETRY_DESCRIPTION,
54
55
  abortLabel: ABORT_LABEL,
55
56
  abortDescription: ABORT_DESCRIPTION,
@@ -79,11 +80,49 @@ function questionOf(spec) {
79
80
  ],
80
81
  };
81
82
  }
83
+ /**
84
+ * One recovery conversation per failure kind, owned by one plugin instance.
85
+ *
86
+ * Tool calls run concurrently, and a Zotero that is down fails all of them:
87
+ * five parallel reads used to put five identical cards in front of the user,
88
+ * each one demanding the same answer. The gate keeps one ask per failure
89
+ * kind in flight and lets every other caller wait on that same answer, so a
90
+ * single card decides for all of them — and each caller then retries, or
91
+ * does not, on its own request. The entry is dropped as soon as the question
92
+ * settles, so a later failure asks again instead of inheriting a stale
93
+ * answer.
94
+ */
95
+ export class ConnectivityRecovery {
96
+ asking = new Map();
97
+ /**
98
+ * Answer one failure kind, asking only when no question for it is in
99
+ * flight.
100
+ * @param code - the failure kind the question belongs to.
101
+ * @param question - the ask to run when this caller is the first; resolves
102
+ * true when the user chose to retry.
103
+ * @returns the answer this caller acts on.
104
+ */
105
+ async ask(code, question) {
106
+ const inFlight = this.asking.get(code);
107
+ if (inFlight !== undefined)
108
+ return await inFlight;
109
+ const answer = question();
110
+ this.asking.set(code, answer);
111
+ try {
112
+ return await answer;
113
+ }
114
+ finally {
115
+ this.asking.delete(code);
116
+ }
117
+ }
118
+ }
82
119
  /**
83
120
  * Run one Zotero request; on a connectivity failure, ask the user how to
84
121
  * proceed and retry at most once when they choose the recommended action.
85
122
  * @param ctx - the plugin context; the question service is looked up
86
123
  * optionally, so headless compositions skip the ask.
124
+ * @param recovery - the instance's recovery gate: callers that failed the
125
+ * same way share one question instead of stacking identical cards.
87
126
  * @param exec - the tool execution (signal and agent) the failure belongs to.
88
127
  * @param run - the request to attempt; must be re-runnable with identical
89
128
  * arguments, because the retry path calls it a second time.
@@ -92,7 +131,7 @@ function questionOf(spec) {
92
131
  * does not choose to retry, the question itself fails, or the retry
93
132
  * fails again.
94
133
  */
95
- export async function withConnectivityAsk(ctx, exec, run) {
134
+ export async function withConnectivityAsk(ctx, recovery, exec, run) {
96
135
  try {
97
136
  return await run();
98
137
  }
@@ -103,14 +142,23 @@ export async function withConnectivityAsk(ctx, exec, run) {
103
142
  if (questions === undefined)
104
143
  throw error;
105
144
  const spec = FAILURE_SPECS[error.code];
106
- let answer;
145
+ let retry;
107
146
  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);
147
+ retry = await recovery.ask(error.code, async () => {
148
+ const request = {
149
+ questions: [questionOf(spec)],
150
+ ...(exec.agent !== undefined ? { agent: exec.agent } : {}),
151
+ signal: exec.signal,
152
+ };
153
+ const answer = await questions.ask(request);
154
+ const answerItem = answer.answers.find((item) => item.id === 'zotero-failure');
155
+ // Matched by label string because the answer protocol carries only
156
+ // selected labels (no stable option ids); the labels are code
157
+ // constants (FAILURE_SPECS), never i18n copy, so a rename breaks the
158
+ // build's contract visibly in one place. Revisit if the protocol
159
+ // gains option ids.
160
+ return (answerItem?.selected ?? []).includes(spec.retryLabel);
161
+ });
114
162
  }
115
163
  catch {
116
164
  // A failed question (no provider, aborted ask, delegated caller) must
@@ -119,13 +167,7 @@ export async function withConnectivityAsk(ctx, exec, run) {
119
167
  throw new HarnessError('tool call aborted', TOOL_ABORTED);
120
168
  throw error;
121
169
  }
122
- const answerItem = answer.answers.find((item) => item.id === 'zotero-failure');
123
- const selected = answerItem?.selected ?? [];
124
- // Matched by label string because the answer protocol carries only
125
- // selected labels (no stable option ids); the labels are code constants
126
- // (FAILURE_SPECS), never i18n copy, so a rename breaks the build's
127
- // contract visibly in one place. Revisit if the protocol gains option ids.
128
- if (!selected.includes(spec.retryLabel))
170
+ if (!retry)
129
171
  throw error;
130
172
  // Outside the catch: a second failure propagates as-is, never re-asking.
131
173
  return await run();
package/lib/ask.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ask.js","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ask.js","sourceRoot":"","sources":["../src/ask.ts"],"names":[],"mappings":"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"}