dsh-reference-anything 0.2.4 → 0.2.5

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.md CHANGED
@@ -22,7 +22,13 @@ One `@` to reference them all.
22
22
 
23
23
  </div>
24
24
 
25
- Within the unified `@` menu of DeepSeek Harness (DSH), reference commands, Skills, workspace files/folders, DSH sessions, and historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
25
+ <div align="center">
26
+
27
+ <img src="./images/demo.gif" alt="dsh-reference-anything demo" width="800" />
28
+
29
+ </div>
30
+
31
+ Within a unified `@` menu of DeepSeek Harness (DSH), reference commands, Skills, workspace files/folders, DSH sessions, and historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
26
32
 
27
33
  This plugin uses OpenCLI to access historical conversations through AI chat sessions that are already logged in. By default, only conversation titles are stored locally, and the agent fetches remote content on demand. An optional offline-mirror mode stores the latest complete conversation bodies locally.
28
34
 
@@ -31,7 +37,7 @@ This plugin uses OpenCLI to access historical conversations through AI chat sess
31
37
 
32
38
  ## News
33
39
 
34
- - **2026-08-19 · v0.2.3** — Added automatic version checks and in-settings updates, Pill/Raw text input rendering modes, and reusable background browser sessions for more reliable OpenCLI synchronization and input interactions.
40
+ - **2026-08-19 · v0.2.4** — Added automatic version checks and in-settings updates, Pill/Raw text input rendering modes, and reusable background browser sessions for more reliable OpenCLI synchronization and input interactions.
35
41
  - **2026-08-18 · v0.2.0** — A redesigned Reference Anything settings page with local session statistics, paginated management, Provider/Profile selection, and sync status checks.
36
42
  - **2026-08-18** — Introduced on-demand read protocol: references default to safe pointers, and the agent reads the body and attachments only after authorization.
37
43
  - **2026-08-17** — Unified ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi under the DSH `@` menu.
@@ -98,14 +104,20 @@ The `@` menu contains five groups: `Commands`, `Skills`, `Files and folders`, `D
98
104
 
99
105
  Available only at the start of the draft. To browse all commands, use `@commands` or the native DSH `/` panel.
100
106
 
107
+ <p align="center"><img src="./images/at-commands.png" alt="Browse DSH commands from the @ menu" width="800" /></p>
108
+
101
109
  #### @Skills — DSH skill library
102
110
 
103
111
  Available only at the start of the draft. To browse all skills, use `@skills:` or the native DSH `/` panel.
104
112
 
113
+ <p align="center"><img src="./images/at-skills.png" alt="Browse DSH skills from the @ menu" width="800" /></p>
114
+
105
115
  #### @Files and folders — workspace files and directories
106
116
 
107
117
  Type `@files:` in the input box to browse all files and folders in the workspace. Search supports fuzzy matching on titles, so both `@cachedes` and `@cache-design` can match “Cache design notes.”
108
118
 
119
+ <p align="center"><img src="./images/at-files.png" alt="Browse workspace files and folders from the @ menu" width="800" /></p>
120
+
109
121
  Features:
110
122
  - Quick reference to workspace files with automatic workspace-boundary validation
111
123
  - File references only write a validated path and type marker into the model context; file content is not preloaded
@@ -115,6 +127,8 @@ Features:
115
127
 
116
128
  Type `@sessions:` to browse locally synced DSH sessions. Sessions are ranked by match quality, with recency as fallback ordering.
117
129
 
130
+ <p align="center"><img src="./images/at-sessions.png" alt="Browse DSH sessions from the @ menu" width="800" /></p>
131
+
118
132
  Search capabilities:
119
133
  - **Title match:** fuzzy search on session titles
120
134
  - **Content search:** when title matches are insufficient, the synced session body is searched; matching excerpts are shown in the candidate row for UI display only and are not injected into model context
@@ -126,6 +140,8 @@ Full browsing is available on the settings page’s paginated list. Session refe
126
140
 
127
141
  Supports historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
128
142
 
143
+ <p align="center"><img src="./images/at-external-conversations.png" alt="Browse external conversations from the @ menu" width="800" /></p>
144
+
129
145
  **Platform filtering:**
130
146
  - Use `@chatgpt:cache` or `@claude:refactor` to filter a specific platform
131
147
  - Short aliases are also accepted, such as `@gpt:` and `@ds:`
package/lib/opencli.js CHANGED
@@ -14,10 +14,10 @@ const SITE = {
14
14
  const REQUIRED_ADAPTER_COMMANDS = ['chatgpt', 'claude', 'gemini', 'deepseek', 'grok', 'kimi'];
15
15
  const ADAPTER_PLUGIN_NAME = 'dsh-chat-history';
16
16
  /**
17
- * A persistent OpenCLI site session owns one mutable browser tab. Serialize
17
+ * A persistent OpenCLI site session owns one mutable browser lease. Serialize
18
18
  * commands that target the same executable/profile/site across runner
19
- * instances so concurrent detail workers cannot navigate that tab over one
20
- * another. Different provider sites still run in parallel.
19
+ * instances so concurrent detail workers cannot navigate or close each
20
+ * other's active tab. Different provider sites still run in parallel.
21
21
  */
22
22
  const SITE_COMMAND_TAILS = new Map();
23
23
  export class OpenCliError extends Error {
@@ -210,9 +210,9 @@ export class OpenCliRunner {
210
210
  async json(site, operation, args, signal) {
211
211
  const queueKey = [this.executable, ...this.prefixArgs, this.profile, site].join('\0');
212
212
  return serializeSiteCommand(queueKey, async () => {
213
- // Pin adapter traffic to one persistent background session. OpenCLI
214
- // otherwise lets caller flags override the adapter lifecycle metadata,
215
- // which could release its tab or make an unattended sync steal focus.
213
+ // Pin adapter traffic to one persistent background session. Persistence
214
+ // suppresses OpenCLI's close-window action; the adapter closes only its
215
+ // completed task tab and leaves the background container reusable.
216
216
  const stdout = await this.raw([
217
217
  site, operation, ...args,
218
218
  '--site-session', 'persistent', '--window', 'background', '-f', 'json',
@@ -38,7 +38,7 @@ async function evaluate(page, script, args, operation) {
38
38
  return parseEvaluate(await page.evaluate(source), operation)
39
39
  }
40
40
 
41
- function assertResult(result, domain, operation) {
41
+ function assertResult(result, domain, operation) {
42
42
  if (result?.code === 'AUTH') throw new AuthRequiredError(domain, result.message || `${operation} requires login`)
43
43
  if (result?.code === 'RATE_LIMIT') {
44
44
  throw new CommandExecutionError(`${operation}: ${result.message || 'provider rate limit reached'}`)
@@ -46,9 +46,26 @@ function assertResult(result, domain, operation) {
46
46
  if (result?.ok !== true || !Array.isArray(result.rows)) {
47
47
  throw new CommandExecutionError(`${operation}: ${result?.message || 'provider adapter did not return rows'}`)
48
48
  }
49
- return result.rows
50
- }
51
-
49
+ return result.rows
50
+ }
51
+
52
+ /**
53
+ * Close only the tab used by this adapter command. The persistent site session
54
+ * prevents OpenCLI from sending its `close-window` lease-release action, while
55
+ * Browser Bridge can keep and reuse the background container window.
56
+ */
57
+ async function inTemporaryTab(page, task) {
58
+ try {
59
+ return await task()
60
+ } finally {
61
+ try {
62
+ await page.closeTab?.()
63
+ } catch {
64
+ // Preserve the provider result/error if the tab or browser closed first.
65
+ }
66
+ }
67
+ }
68
+
52
69
  export function registerProvider(config) {
53
70
  const browserSession = SYNC_BROWSER_SESSION
54
71
 
@@ -64,7 +81,7 @@ export function registerProvider(config) {
64
81
  navigateBefore: false,
65
82
  args: [],
66
83
  columns: ['identity'],
67
- func: async (page) => {
84
+ func: async (page) => inTemporaryTab(page, async () => {
68
85
  await page.goto(config.home, { settleMs: 500 })
69
86
  const result = await evaluate(page, config.whoamiScript, {}, `${config.site} whoami`)
70
87
  if (result?.code === 'AUTH') throw new AuthRequiredError(config.domain)
@@ -72,7 +89,7 @@ export function registerProvider(config) {
72
89
  throw new CommandExecutionError(`${config.site} whoami: ${result?.message || 'stable account identity unavailable'}`)
73
90
  }
74
91
  return [{ identity: result.identity }]
75
- },
92
+ }),
76
93
  })
77
94
 
78
95
  cli({
@@ -90,7 +107,7 @@ export function registerProvider(config) {
90
107
  help: 'ISO 8601 instant; stop paging once the listing predates it. Honoured only while the provider pages newest-first',
91
108
  }, { name: 'accountScope', help: 'Previously observed hashed account scope; mismatch forces a full listing' }],
92
109
  columns: SYNC_INDEX_COLUMNS,
93
- func: async (page, kwargs) => {
110
+ func: async (page, kwargs) => inTemporaryTab(page, async () => {
94
111
  const since = String(kwargs.since || '').trim()
95
112
  if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
96
113
  await page.goto(config.home, { settleMs: 1200 })
@@ -110,7 +127,7 @@ export function registerProvider(config) {
110
127
  { kind: 'identity', identity: identity.identity, sinceApplied: effectiveSince },
111
128
  ...rows.map(row => ({ kind: 'conversation', identity: '', sinceApplied: '', ...row })),
112
129
  ]
113
- },
130
+ }),
114
131
  })
115
132
 
116
133
  cli({
@@ -128,7 +145,7 @@ export function registerProvider(config) {
128
145
  help: 'ISO 8601 instant; stop paging once the listing predates it. Honoured only while the provider pages newest-first',
129
146
  }],
130
147
  columns: HISTORY_COLUMNS,
131
- func: async (page, kwargs) => {
148
+ func: async (page, kwargs) => inTemporaryTab(page, async () => {
132
149
  const since = String(kwargs.since || '').trim()
133
150
  if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
134
151
  await page.goto(config.home, { settleMs: 1200 })
@@ -138,7 +155,7 @@ export function registerProvider(config) {
138
155
  `${config.site} history-all`,
139
156
  )
140
157
  return rows
141
- },
158
+ }),
142
159
  })
143
160
 
144
161
  cli({
@@ -156,7 +173,7 @@ export function registerProvider(config) {
156
173
  { name: 'accountScope', help: 'Expected hashed account scope; mismatch refuses the read' },
157
174
  ],
158
175
  columns: DETAIL_COLUMNS,
159
- func: async (page, kwargs) => {
176
+ func: async (page, kwargs) => inTemporaryTab(page, async () => {
160
177
  const id = String(kwargs.id || '').trim()
161
178
  if (!id) throw new ArgumentError('id must be a non-empty conversation id')
162
179
  await page.goto(config.home, { settleMs: 600 })
@@ -180,7 +197,7 @@ export function registerProvider(config) {
180
197
  const rows = assertResult(result, config.domain, `${config.site} detail`)
181
198
  if (rows.length === 0) throw new EmptyResultError(`${config.site} detail`, `No messages found for ${id}`)
182
199
  return rows
183
- },
200
+ }),
184
201
  })
185
202
 
186
203
  cli({
@@ -199,7 +216,7 @@ export function registerProvider(config) {
199
216
  { name: 'maxBytes', type: 'int', default: 26214400, help: 'Maximum decoded bytes' },
200
217
  ],
201
218
  columns: ['attachmentId', 'name', 'mimeType', 'size', 'status', 'localPath'],
202
- func: async (page, kwargs) => {
219
+ func: async (page, kwargs) => inTemporaryTab(page, async () => {
203
220
  const locator = String(kwargs.locator || '').trim()
204
221
  const output = resolve(String(kwargs.output || ''))
205
222
  const maxBytes = Number(kwargs.maxBytes ?? 26_214_400)
@@ -229,15 +246,14 @@ export function registerProvider(config) {
229
246
  status: 'available',
230
247
  localPath: output,
231
248
  }]
232
- },
249
+ }),
233
250
  })
234
251
  }
235
252
 
236
253
  /**
237
254
  * Keep one stable, background Browser Bridge session per provider site.
238
- * OpenCLI maps persistent sessions to `site:<config.site>` and keeps their tab
239
- * lease after both successful and failed commands, so later sync work reuses
240
- * the same tab. If the user closes it manually, OpenCLI recreates it on demand.
255
+ * Persistence suppresses OpenCLI's `close-window` action; inTemporaryTab closes
256
+ * only the finished command's tab so the reusable container window stays alive.
241
257
  */
242
258
  export const SYNC_BROWSER_SESSION = Object.freeze({
243
259
  siteSession: 'persistent',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-reference-anything",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Reference locally mirrored ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi conversations from DeepSeek Harness",
5
5
  "license": "MIT",
6
6
  "publishConfig": {