dsh-reference-anything 0.4.0 → 0.4.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.md
CHANGED
|
@@ -70,18 +70,16 @@ Type `@`, search across enabled sources, and insert the selected reference into
|
|
|
70
70
|
|
|
71
71
|
**The External conversations group** — and only that group — uses OpenCLI to reach AI chat sessions you are already logged into. By default it stores conversation titles locally and the agent fetches remote content on demand; an optional offline-mirror mode stores the latest complete bodies locally instead. **Local agent conversations need none of that**: those transcripts are already files on your own disk, so that group reads them directly, with no browser, no OpenCLI, and nothing mirrored.
|
|
72
72
|
|
|
73
|
-
> [!IMPORTANT]
|
|
74
|
-
> v0.4.0 targets DSH `>=0.1.2-rc.1` and uses the RC1 Skills Remote, Session snapshot API, native `@` trigger menu, official file/session Remotes, and native Composer reference rendering. It is incompatible with DSH versions older than `0.1.2-rc.1`.
|
|
75
|
-
> The `0.3.x` line remains on the current stable DSH SDK (`0.1.0-rc.8 ~ 0.1.1-rc.2`). Due to breaking changes, starting with version `v0.4.0`, development releases will target DSH `v0.1.2-rc.1` or newer, use the npm `alpha` tag, and no longer support older DSH releases.
|
|
76
|
-
|
|
77
73
|
> [!NOTE]
|
|
78
74
|
> DSH is currently in Beta, so its underlying capabilities and interfaces may change as it evolves. This plugin will adapt alongside those changes. Because of some current DSH limitations, parts of the implementation may not yet be ideal; we will continue to follow DSH updates and improve the plugin over time. See the relevant sections below for specific limitations and usage notes.
|
|
79
75
|
|
|
80
76
|
## 📰 News
|
|
81
77
|
|
|
78
|
+
- **2026-09-25 · v0.4.1** — We're back!!🎉🎉 Improved local-agent session titles by reading Codex's own `session_index.jsonl` and added Pi/Codex title support. Restored the native DSH handling for `@` menu group headings to avoid translucent overlap and unnecessary custom blur rendering; bounded index reads to 16 MiB for predictable resource use.
|
|
79
|
+
|
|
82
80
|
- **2026-09-03 · v0.4.0** — **Compatibility: DSH `>=0.1.2-rc.1`.** Migrated Skills discovery to the DSH RC1 Skills Remote, restored historical reference grants through the RC1 Session snapshot API, and verified the plugin against DSH `0.1.2-rc.1`.
|
|
83
81
|
|
|
84
|
-
- **2026-08-28 · v0.3.3** — Improved dark-theme backgrounds for the reference menu and settings panels, added project screenshot metadata, and refreshed the documentation. This
|
|
82
|
+
- **2026-08-28 · v0.3.3** — Improved dark-theme backgrounds for the reference menu and settings panels, added project screenshot metadata, and refreshed the documentation. This was the final release line targeting the current stable DeepSeek Harness SDK; `v0.4.0` migrated to DeepSeek Harness `v0.1.2-rc.1` under the npm `alpha` tag and does not support older DSH releases.
|
|
85
83
|
- **2026-08-25 · v0.3.2** — Added local-agent conversation references, with detected session counts and custom-folder support. Web AI sync can be age-limited; cloud drives now support folder browsing and quick download-folder access. Settings include update notes, and local-agent tool details stay concise unless expanded.
|
|
86
84
|
- **2026-08-20 · v0.3.0** — Completed the native DSH `@` integration: five independently configurable sources, official file/session Remotes, native Composer references with source logos, in-place expand/collapse and sync actions, and a one-click switch between Reference Anything and the official DSH `@` list. The legacy `dsh-file:` protocol and custom Composer interaction layer were removed.
|
|
87
85
|
|
|
@@ -110,7 +108,7 @@ Install the stable `0.3.x` line for the current stable DSH SDK:
|
|
|
110
108
|
dsh plugin --profile web add dsh-reference-anything@latest
|
|
111
109
|
```
|
|
112
110
|
|
|
113
|
-
Install `v0.4.
|
|
111
|
+
Install `v0.4.1` on DSH `>=0.1.2-rc.1`:
|
|
114
112
|
|
|
115
113
|
```powershell
|
|
116
114
|
dsh plugin --profile web add dsh-reference-anything@alpha
|
package/lib/client.js
CHANGED
|
@@ -29617,8 +29617,6 @@ var css = `
|
|
|
29617
29617
|
caret, selection, wrapping, and message geometry. */
|
|
29618
29618
|
[data-composer-card] [role="listbox"]:has([role="presentation"][data-source]){box-sizing:border-box!important;width:100%!important;min-width:0!important;max-width:100%!important}
|
|
29619
29619
|
[data-trigger-menu]:has(> [role="listbox"] [role="presentation"][data-source]){border-radius:22px}
|
|
29620
|
-
[data-composer-card] [role="listbox"] [role="presentation"][data-source]{position:sticky;top:0;z-index:2;box-sizing:border-box;padding-top:6px!important;background:var(--dsw-specific-menu,var(--dsw-alias-background-primary,var(--dsw-alias-bg-layer-1,#fff)))}
|
|
29621
|
-
[data-composer-card] [role="listbox"] [role="presentation"][data-source]:not(:first-child){margin-top:8px}
|
|
29622
29620
|
[data-composer-card] [role="listbox"] [role="option"]{scroll-margin-block-start:30px;scroll-margin-block-end:4px}
|
|
29623
29621
|
[data-composer-card] [role="listbox"]>[data-dsh-ref-menu-settling]{overflow-anchor:none!important}
|
|
29624
29622
|
[data-composer-card] [role="listbox"]>[data-dsh-ref-menu-settling] [role="option"][aria-selected="false"]:hover{background:transparent!important}
|
|
@@ -82,9 +82,11 @@ export const piAdapter = {
|
|
|
82
82
|
createdAt ??= parseTimestamp(record['timestamp']);
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
|
-
// A rename writes another `session_info`, so the newest one wins.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// A rename writes another `session_info`, so the newest one wins. Some
|
|
86
|
+
// Pi-compatible writers call the same field `title`.
|
|
87
|
+
if (record['type'] === 'session_info') {
|
|
88
|
+
named = stringField(record, 'name') ?? stringField(record, 'title') ?? named;
|
|
89
|
+
}
|
|
88
90
|
}
|
|
89
91
|
for (const line of headLines) {
|
|
90
92
|
const record = parseRecord(line);
|
|
@@ -643,7 +643,9 @@ function foldsToNothing(adapter, lines, options) {
|
|
|
643
643
|
return adapter.flush(state, options).length === 0;
|
|
644
644
|
}
|
|
645
645
|
function labelFor(descriptor, bookmark) {
|
|
646
|
-
|
|
646
|
+
// Some file-backed agents expose a title index beside the transcript. It is
|
|
647
|
+
// fresher than a persisted first-prompt bookmark and must win when present.
|
|
648
|
+
const title = descriptor.title?.trim() || bookmark?.title.trim() || '';
|
|
647
649
|
return title === '' ? basename(descriptor.relPath) : title;
|
|
648
650
|
}
|
|
649
651
|
/** The schema's own defaults, as a plain object. */
|
|
@@ -40,6 +40,15 @@ export interface TranscriptDescriptor {
|
|
|
40
40
|
readonly relPath: string;
|
|
41
41
|
readonly mtimeMs: number;
|
|
42
42
|
readonly size: number;
|
|
43
|
+
/**
|
|
44
|
+
* A title supplied by an index beside a file-backed transcript.
|
|
45
|
+
*
|
|
46
|
+
* Codex keeps its user-facing session title in `~/.codex/session_index.jsonl`,
|
|
47
|
+
* while the rollout itself only contains the opening prompt. This value is
|
|
48
|
+
* kept on the descriptor so a fresh index title can override an older
|
|
49
|
+
* first-prompt bookmark without changing the transcript adapter contract.
|
|
50
|
+
*/
|
|
51
|
+
readonly title?: string;
|
|
43
52
|
/**
|
|
44
53
|
* The session row, for a database-backed format only.
|
|
45
54
|
*
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* @module dsh-reference-anything/local-agent/scan
|
|
18
18
|
*/
|
|
19
19
|
import { createReadStream } from 'node:fs';
|
|
20
|
-
import { open, readdir, stat } from 'node:fs/promises';
|
|
21
|
-
import { join, relative } from 'node:path';
|
|
20
|
+
import { open, readdir, readFile, stat } from 'node:fs/promises';
|
|
21
|
+
import { basename, join, relative, resolve } from 'node:path';
|
|
22
22
|
import { ReferenceAnythingError } from "../../errors.js";
|
|
23
23
|
import { withDatabase } from "./sqlite.js";
|
|
24
24
|
import { isQueryAdapter } from "./types.js";
|
|
@@ -26,6 +26,7 @@ import { isQueryAdapter } from "./types.js";
|
|
|
26
26
|
export const SESSION_SEPARATOR = '#';
|
|
27
27
|
/** Directory depth walked below each root. */
|
|
28
28
|
const MAX_DEPTH = 6;
|
|
29
|
+
const MAX_CODEX_TITLE_INDEX_BYTES = 16 * 1024 * 1024;
|
|
29
30
|
/** Probe windows, in bytes, tried in order until enough lines are recovered. */
|
|
30
31
|
const PROBE_STEPS = [32 * 1024, 256 * 1024, 2 * 1024 * 1024];
|
|
31
32
|
/** Chunk size for walking a large transcript backwards from its end. */
|
|
@@ -48,12 +49,15 @@ export async function listTranscripts(roots, adapters, maxTranscripts, signal) {
|
|
|
48
49
|
const adapter = adapters.get(root.kind);
|
|
49
50
|
if (adapter === undefined)
|
|
50
51
|
continue;
|
|
51
|
-
|
|
52
|
+
const titles = root.kind === 'codex' && !isQueryAdapter(adapter)
|
|
53
|
+
? await readCodexTitleIndex(root.path)
|
|
54
|
+
: undefined;
|
|
55
|
+
await walk(root, root.path, 0, adapter, found, signal, maxTranscripts, titles);
|
|
52
56
|
}
|
|
53
57
|
found.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
54
58
|
return found.slice(0, maxTranscripts);
|
|
55
59
|
}
|
|
56
|
-
async function walk(root, dir, depth, adapter, found, signal, maxSessions = 200) {
|
|
60
|
+
async function walk(root, dir, depth, adapter, found, signal, maxSessions = 200, titles) {
|
|
57
61
|
signal?.throwIfAborted();
|
|
58
62
|
if (depth > MAX_DEPTH)
|
|
59
63
|
return;
|
|
@@ -63,7 +67,7 @@ async function walk(root, dir, depth, adapter, found, signal, maxSessions = 200)
|
|
|
63
67
|
for (const entry of entries) {
|
|
64
68
|
const path = join(dir, entry.name);
|
|
65
69
|
if (entry.isDirectory()) {
|
|
66
|
-
await walk(root, path, depth + 1, adapter, found, signal, maxSessions);
|
|
70
|
+
await walk(root, path, depth + 1, adapter, found, signal, maxSessions, titles);
|
|
67
71
|
continue;
|
|
68
72
|
}
|
|
69
73
|
if (!entry.isFile())
|
|
@@ -78,9 +82,71 @@ async function walk(root, dir, depth, adapter, found, signal, maxSessions = 200)
|
|
|
78
82
|
found.push(...await expandSessions(root.kind, path, relPath, stats.mtimeMs, stats.size, adapter, maxSessions));
|
|
79
83
|
continue;
|
|
80
84
|
}
|
|
81
|
-
found.push({
|
|
85
|
+
found.push({
|
|
86
|
+
kind: root.kind,
|
|
87
|
+
path,
|
|
88
|
+
relPath,
|
|
89
|
+
mtimeMs: stats.mtimeMs,
|
|
90
|
+
size: stats.size,
|
|
91
|
+
...titles === undefined ? {} : titleForCodexPath(path, titles),
|
|
92
|
+
});
|
|
82
93
|
}
|
|
83
94
|
}
|
|
95
|
+
/** Read the Codex title index once per configured sessions root. */
|
|
96
|
+
async function readCodexTitleIndex(sessionsRoot) {
|
|
97
|
+
const indexPath = resolve(sessionsRoot, '..', 'session_index.jsonl');
|
|
98
|
+
const indexStats = await stat(indexPath).catch(() => undefined);
|
|
99
|
+
// Keep discovery bounded; an unexpectedly large index must not turn a
|
|
100
|
+
// keystroke-triggered scan into an unbounded allocation.
|
|
101
|
+
if (indexStats === undefined || indexStats.size > MAX_CODEX_TITLE_INDEX_BYTES)
|
|
102
|
+
return undefined;
|
|
103
|
+
const raw = await readFile(indexPath, 'utf8').catch(() => undefined);
|
|
104
|
+
if (raw === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const byPath = new Map();
|
|
107
|
+
const bySessionId = new Map();
|
|
108
|
+
for (const line of raw.split(/\r?\n/u)) {
|
|
109
|
+
if (line.trim() === '')
|
|
110
|
+
continue;
|
|
111
|
+
let record;
|
|
112
|
+
try {
|
|
113
|
+
record = JSON.parse(line);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (typeof record !== 'object' || record === null)
|
|
119
|
+
continue;
|
|
120
|
+
const row = record;
|
|
121
|
+
const title = typeof row['thread_name'] === 'string' ? row['thread_name'].trim() : '';
|
|
122
|
+
if (title === '')
|
|
123
|
+
continue;
|
|
124
|
+
const id = typeof row['id'] === 'string' ? row['id'].trim() : '';
|
|
125
|
+
if (id !== '')
|
|
126
|
+
bySessionId.set(id, title);
|
|
127
|
+
const rolloutPath = typeof row['rollout_path'] === 'string' ? row['rollout_path'].trim() : '';
|
|
128
|
+
if (rolloutPath !== '')
|
|
129
|
+
byPath.set(normalizeIndexedPath(rolloutPath), title);
|
|
130
|
+
}
|
|
131
|
+
return { byPath, bySessionId };
|
|
132
|
+
}
|
|
133
|
+
function titleForCodexPath(path, index) {
|
|
134
|
+
const direct = index.byPath.get(normalizeIndexedPath(path));
|
|
135
|
+
if (direct !== undefined)
|
|
136
|
+
return { title: direct };
|
|
137
|
+
const match = basename(path).match(/-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:_[^.]*)?\.jsonl$/iu);
|
|
138
|
+
const sessionId = match?.[1];
|
|
139
|
+
const title = sessionId === undefined ? undefined : index.bySessionId.get(sessionId);
|
|
140
|
+
return title === undefined ? {} : { title };
|
|
141
|
+
}
|
|
142
|
+
/** Normalize Windows paths from the index and from the active filesystem. */
|
|
143
|
+
function normalizeIndexedPath(path) {
|
|
144
|
+
return path
|
|
145
|
+
.replace(/^\\\\\?\\/u, '')
|
|
146
|
+
.replace(/[\\/]+/gu, '\\')
|
|
147
|
+
.replace(/\\$/u, '')
|
|
148
|
+
.toLocaleLowerCase();
|
|
149
|
+
}
|
|
84
150
|
/**
|
|
85
151
|
* Turn one database into a descriptor per conversation inside it.
|
|
86
152
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-reference-anything",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Reference commands, skills, workspace files, DSH sessions, local agent transcripts, cloud-drive files, and mirrored web-chat conversations from one DeepSeek Harness @ menu",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|