dsh-reference-anything 0.4.0 → 0.4.2

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,18 @@ 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-28 · v0.4.2 — Important update** — Fixed a fatal plugin compatibility error with the current DSH interface. During plugin loading, DSH now requires every strict `agentSearch` registration codec to provide a `create()` validator factory; v0.4.1 supplied only validation rules, so DSH rejected the plugin before search could run. This release adds the required factory and fixes command icon sizing plus bilingual command labels in the native picker.
79
+
80
+ - **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.
81
+
82
82
  - **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
83
 
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 is the final release line targeting the current stable DeepSeek Harness SDK; the next development release, `v0.4.0`, will migrate to DeepSeek Harness `v0.1.2-rc.1` under the npm `alpha` tag and will not support older DSH releases.
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 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
85
  - **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
86
  - **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
87
 
@@ -110,7 +110,7 @@ Install the stable `0.3.x` line for the current stable DSH SDK:
110
110
  dsh plugin --profile web add dsh-reference-anything@latest
111
111
  ```
112
112
 
113
- Install `v0.4.0` on DSH `>=0.1.2-rc.1`:
113
+ Install `v0.4.2` on DSH `>=0.1.2-rc.1`:
114
114
 
115
115
  ```powershell
116
116
  dsh plugin --profile web add dsh-reference-anything@alpha
package/lib/client.js CHANGED
@@ -14989,7 +14989,7 @@ var agentLookup = {
14989
14989
  wire: "agentId",
14990
14990
  source: "lookup",
14991
14991
  lookup: "agent",
14992
- codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: external_exports.string().min(1) }
14992
+ codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: external_exports.string().min(1), create: () => external_exports.string().min(1) }
14993
14993
  };
14994
14994
  var searchInputSchema = external_exports.object({
14995
14995
  query: external_exports.string(),
@@ -15198,7 +15198,7 @@ var REFERENCE_ANYTHING_INVOCATIONS = [
15198
15198
  descriptor("openListReindex", [{ name: "input", wire: "input", source: "json", codec: strict("OpenListMountInput", openListMountInputSchema) }], strict("OpenListReindex", external_exports.object({ supported: external_exports.boolean(), reason: external_exports.string().optional() }).readonly()), true)
15199
15199
  ];
15200
15200
  function strict(type, schema) {
15201
- return { mode: "strict", typeSymbol: `dsh-reference-anything#${type}`, schema };
15201
+ return { mode: "strict", typeSymbol: `dsh-reference-anything#${type}`, schema, create: () => schema };
15202
15202
  }
15203
15203
  function descriptor(method, parameters, result, cancelled = false) {
15204
15204
  return {
@@ -16883,7 +16883,13 @@ var PICKER_ICON_MARKER = {
16883
16883
  font: "\uE114",
16884
16884
  refresh: "\uE115",
16885
16885
  agent: "\uE116",
16886
- drive: "\uE117"
16886
+ drive: "\uE117",
16887
+ goal: "\uE125",
16888
+ plan: "\uE126",
16889
+ feedback: "\uE127",
16890
+ compact: "\uE128",
16891
+ permission: "\uE129",
16892
+ export: "\uE12A"
16887
16893
  };
16888
16894
  var SESSION_ICON_MARKER = PICKER_ICON_MARKER.session;
16889
16895
  var SKILL_ICON_MARKER = PICKER_ICON_MARKER.skill;
@@ -16937,6 +16943,25 @@ var PICKER_ICON_NODES = {
16937
16943
  { tag: "path", attrs: { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" } },
16938
16944
  { tag: "path", attrs: { d: "M16 16h5v5" } }
16939
16945
  ],
16946
+ goal: [
16947
+ { tag: "path", attrs: { d: "M14.5001 8C14.5 9.28552 14.1188 10.5422 13.4045 11.611C12.6903 12.6799 11.6752 13.5129 10.4875 14.0049C9.29982 14.4968 7.99295 14.6255 6.73212 14.3747C5.4713 14.124 4.31314 13.505 3.4041 12.596C2.49514 11.687 1.87614 10.5288 1.62537 9.26798C1.37459 8.00716 1.50331 6.70028 1.99525 5.51261C2.48719 4.32494 3.32025 3.30981 4.3891 2.59557C5.45795 1.88134 6.71458 1.50008 8.0001 1.5" } },
16948
+ { tag: "path", attrs: { d: "M11.5 8C11.5001 8.69227 11.2948 9.36901 10.9102 9.94463C10.5257 10.5202 9.97901 10.9689 9.33944 11.2338C8.69986 11.4987 7.99609 11.5681 7.31712 11.433C6.63816 11.2979 6.01449 10.9645 5.52501 10.475C5.03548 9.98552 4.70209 9.36185 4.56702 8.68289C4.43195 8.00392 4.50127 7.30015 4.76619 6.66057C5.03112 6.021 5.47976 5.47436 6.05538 5.08978C6.631 4.70519 7.30774 4.49995 8.00001 4.5" } },
16949
+ { tag: "path", attrs: { d: "M8.00024 7.99976L11.2 4.80005" } }
16950
+ ],
16951
+ feedback: [{ tag: "path", attrs: { d: "M4.74024 9.11029L1.82882 7.79865L12.9665 2.60943L12.3914 13.1388L6.12677 9.83657M8.44336 11.0825L6.2832 13.2843V9.86772L12.972 2.78748" } }],
16952
+ plan: [
16953
+ { tag: "path", attrs: { d: "M4.9375 5.90295H11.0625" } },
16954
+ { tag: "path", attrs: { d: "M4.9375 9.02991H8.27841" } },
16955
+ { tag: "path", attrs: { d: "M12.5 1.32617C13.3039 1.32617 14 1.95171 14 2.77637V7.61328L13 8.68164V2.77637C13 2.55186 12.8007 2.32617 12.5 2.32617H3.5C3.1993 2.32617 3 2.55186 3 2.77637V13.2246C3.00044 13.4489 3.19963 13.6738 3.5 13.6738H8.32812L7.39258 14.6738H3.5C2.69637 14.6738 2.00042 14.0489 2 13.2246V2.77637C2 1.95171 2.69613 1.32617 3.5 1.32617H12.5Z" } },
16956
+ { tag: "path", attrs: { d: "M8.97212 14.3693C9.17511 14.5723 9.37811 14.7753 9.5811 14.9783C9.67012 14.8953 9.75914 14.8123 9.84815 14.7293C11.4505 13.2352 13.0528 11.7411 14.6551 10.247C14.7441 10.164 14.8331 10.081 14.9221 9.99803C14.5989 9.6748 14.2756 9.35157 13.9524 9.02834C13.8694 9.11736 13.7864 9.20637 13.7034 9.29539C12.2093 10.8977 10.7152 12.5 9.22113 14.1023C9.13813 14.1913 9.05513 14.2803 8.97212 14.3693Z" } }
16957
+ ],
16958
+ permission: [
16959
+ { tag: "path", attrs: { d: "M6.59624 2.14853C7.50155 1.80917 8.49914 1.80919 9.40444 2.14859L13.9245 3.84317V7.11961C13.9245 11.6089 10.5565 13.5975 8.00035 14.5779C5.44423 13.5975 2.07544 11.6089 2.07544 7.11961V3.84317L6.59624 2.14853Z" } },
16960
+ { tag: "path", attrs: { d: "M8 4.39209V9.89209" } },
16961
+ { tag: "path", attrs: { d: "M8 10.8081V11.8081" } }
16962
+ ],
16963
+ compact: [{ tag: "circle", attrs: { cx: "8", cy: "8", r: "6.5" } }, { tag: "path", attrs: { d: "M8 1.5A6.5 6.5 0 0 1 14.5 8" } }],
16964
+ export: [{ tag: "path", attrs: { d: "M8 1.95317V10.0469M4.25 6.29688L8 10.0469L11.75 6.29688M1.5 10.0469V13.158C1.5 13.3937 1.60536 13.6198 1.79289 13.7865C1.98043 13.9532 2.23478 14.0469 2.5 14.0469H13.5C13.7652 14.0469 14.0198 13.9532 14.2071 13.7865C14.3946 13.6198 14.5 13.3937 14.5 13.158V10.0469" } }],
16940
16965
  // FolderClosed
16941
16966
  folder: [
16942
16967
  { tag: "path", attrs: { d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" } },
@@ -17667,6 +17692,48 @@ function decodeDriveReference(value) {
17667
17692
  function formatDriveMention(reference) {
17668
17693
  return `@[${escapeLabel(reference.label)}](${driveReferenceUri(reference.id)})`;
17669
17694
  }
17695
+ var COMMAND_ICON_KIND = {
17696
+ goal: "goal",
17697
+ plan: "plan",
17698
+ feedback: "feedback",
17699
+ compact: "compact",
17700
+ permission: "permission",
17701
+ export: "export"
17702
+ };
17703
+ var COMMAND_DESCRIPTION_EN = {
17704
+ goal: "Set or view the goal for a long-running task",
17705
+ plan: "Enter or leave plan mode",
17706
+ feedback: "Record feedback about this session",
17707
+ compact: "Compact older conversation history",
17708
+ permission: "Switch the permission preset (sandbox mode + approval policy)",
17709
+ export: "Download this Session log as a ZIP archive"
17710
+ };
17711
+ var COMMAND_DESCRIPTION_ZH = {
17712
+ goal: "\u8BBE\u7F6E\u6216\u67E5\u770B\u957F\u671F\u4EFB\u52A1\u76EE\u6807",
17713
+ plan: "\u8FDB\u5165\u6216\u9000\u51FA\u8BA1\u5212\u6A21\u5F0F",
17714
+ feedback: "\u53D1\u9001\u5173\u4E8E\u5F53\u524D\u4F1A\u8BDD\u7684\u53CD\u9988",
17715
+ compact: "\u538B\u7F29\u4EE5\u4E0A\u5BF9\u8BDD\u5185\u5BB9",
17716
+ permission: "\u5207\u6362\u6743\u9650\u9884\u8BBE\uFF08\u6C99\u7BB1\u6A21\u5F0F\u4E0E\u5BA1\u6279\u7B56\u7565\uFF09",
17717
+ export: "\u5C06\u5F53\u524D\u4F1A\u8BDD\u5185\u5BB9\u5BFC\u51FA\u4E3A ZIP"
17718
+ };
17719
+ var COMMAND_LABEL_EN = {
17720
+ goal: "Goal",
17721
+ plan: "Plan",
17722
+ feedback: "Feedback",
17723
+ compact: "Compact",
17724
+ permission: "Permission",
17725
+ export: "Export",
17726
+ model: "Model"
17727
+ };
17728
+ var COMMAND_LABEL_ZH = {
17729
+ goal: "\u76EE\u6807",
17730
+ plan: "\u8BA1\u5212",
17731
+ feedback: "\u53CD\u9988",
17732
+ compact: "\u538B\u7F29",
17733
+ permission: "\u6743\u9650",
17734
+ export: "\u4E0B\u8F7D\u65E5\u5FD7",
17735
+ model: "\u6A21\u578B"
17736
+ };
17670
17737
  function createCommandSource(load, t = fallback, options = { ...DEFAULT_SOURCE_OPTIONS, order: 0 }) {
17671
17738
  const source = {
17672
17739
  trigger: "@",
@@ -17676,13 +17743,19 @@ function createCommandSource(load, t = fallback, options = { ...DEFAULT_SOURCE_O
17676
17743
  const scoped = scopedQuery(query, "commands");
17677
17744
  if (scoped === void 0) return [];
17678
17745
  const needle = scoped.toLocaleLowerCase();
17679
- return (await load(session.sessionId, signal)).filter((row) => row.name.toLocaleLowerCase().includes(needle)).map((row) => ({
17680
- name: row.name,
17681
- description: row.description,
17682
- hint: row.input?.hint,
17683
- icon: COMMAND_ICON_MARKER,
17684
- value: encodeCandidate({ kind: "command", name: row.name })
17685
- }));
17746
+ return (await load(session.sessionId, signal)).filter((row) => row.name.toLocaleLowerCase().includes(needle)).map((row) => {
17747
+ return {
17748
+ name: (() => {
17749
+ const key = row.name.toLocaleLowerCase();
17750
+ const zh2 = navigator.language.toLowerCase().startsWith("zh");
17751
+ return zh2 ? `${COMMAND_LABEL_ZH[key] ?? row.name} ${row.name}` : COMMAND_LABEL_EN[key] ?? row.name;
17752
+ })(),
17753
+ description: (navigator.language.toLowerCase().startsWith("zh") ? COMMAND_DESCRIPTION_ZH[row.name.toLocaleLowerCase()] : COMMAND_DESCRIPTION_EN[row.name.toLocaleLowerCase()]) ?? row.description,
17754
+ hint: row.input?.hint,
17755
+ icon: PICKER_ICON_MARKER[COMMAND_ICON_KIND[row.name.toLocaleLowerCase()] ?? "command"],
17756
+ value: encodeCandidate({ kind: "command", name: row.name })
17757
+ };
17758
+ });
17686
17759
  },
17687
17760
  onPick({ candidate }) {
17688
17761
  const value = decodeCandidate(candidate.value);
@@ -29617,16 +29690,17 @@ var css = `
29617
29690
  caret, selection, wrapping, and message geometry. */
29618
29691
  [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
29692
  [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
29693
  [data-composer-card] [role="listbox"] [role="option"]{scroll-margin-block-start:30px;scroll-margin-block-end:4px}
29623
29694
  [data-composer-card] [role="listbox"]>[data-dsh-ref-menu-settling]{overflow-anchor:none!important}
29624
29695
  [data-composer-card] [role="listbox"]>[data-dsh-ref-menu-settling] [role="option"][aria-selected="false"]:hover{background:transparent!important}
29625
29696
  [data-composer-card] [role="listbox"] [role="option"][data-dsh-ref-menu-action]{color:var(--dsw-alias-label-tertiary,#737780)!important}
29626
29697
  [data-composer-card] [role="listbox"] [role="option"][data-dsh-ref-menu-action]>span:last-child:not(:first-child){display:none!important}
29627
29698
  [data-composer-card] [role="listbox"] .dsh_ref_projected_icon,[data-composer-card] [role="listbox"] .dsh_ref_picker_icon{display:inline-flex!important;align-items:center!important;justify-content:center!important;flex:none;width:16px!important;height:16px!important;line-height:1!important;color:var(--dsw-alias-label-secondary,#5f636b)}
29699
+ [data-composer-card] [role="listbox"] .dsh_ref_command_icon{width:18px!important;height:18px!important}
29700
+ [data-composer-card] [role="listbox"] .dsh_ref_command_icon>svg{width:18px!important;height:18px!important}
29628
29701
  [data-composer-card] [role="listbox"] .dsh_ref_dual_icon{width:36px!important;gap:4px}
29629
29702
  [data-composer-card] [role="listbox"] :is(.dsh_ref_projected_icon,.dsh_ref_picker_icon)>svg{display:block;width:16px;height:16px;overflow:visible}
29703
+ [data-composer-card] [role="listbox"] .dsh_ref_command_icon,[data-composer-card] [role="listbox"] .dsh_ref_command_icon>svg{width:18px!important;height:18px!important}
29630
29704
  [data-composer-card] [data-decoration="chip"][data-dsh-ref-chip-icon]>:first-child>svg{visibility:hidden!important}
29631
29705
  [data-composer-card] [data-decoration="chip"][data-dsh-ref-chip-icon]>:first-child:after{content:"";position:absolute;top:50%;left:50%;display:block;width:16px;height:16px;transform:translate(-50%,-50%);background:currentColor;-webkit-mask:var(--dsh-ref-chip-icon-mask) center/contain no-repeat;mask:var(--dsh-ref-chip-icon-mask) center/contain no-repeat;pointer-events:none}
29632
29706
  [data-composer-card] [data-composer-chip][data-dsh-ref-chip-icon]>:first-child>svg{background:currentColor;-webkit-mask:var(--dsh-ref-chip-icon-mask) center/contain no-repeat;mask:var(--dsh-ref-chip-icon-mask) center/contain no-repeat}
@@ -29971,6 +30045,7 @@ function adoptReferenceIconProjection() {
29971
30045
  node2.replaceChildren(createPickerIcon(kind), ...secondary === void 0 ? [] : [createPickerIcon(secondary)]);
29972
30046
  node2.classList.remove("dsh_ref_projected_icon");
29973
30047
  node2.classList.add("dsh_ref_picker_icon");
30048
+ node2.classList.toggle("dsh_ref_command_icon", ["goal", "plan", "feedback", "compact", "permission", "export"].includes(kind));
29974
30049
  node2.classList.toggle("dsh_ref_dual_icon", secondary !== void 0);
29975
30050
  node2.dataset.dshRefMenuIcon = kind;
29976
30051
  };
@@ -29996,7 +30071,10 @@ function adoptReferenceIconProjection() {
29996
30071
  const source = heading2?.getAttribute("data-source");
29997
30072
  const name2 = option.children.item(1)?.textContent?.trim() ?? "";
29998
30073
  const description = option.children.item(2)?.textContent?.trim() ?? "";
29999
- if (source === "Commands") return paintPicker(icon, "command");
30074
+ if (source === "Commands") {
30075
+ const commandIcons = { goal: "goal", plan: "plan", feedback: "feedback", compact: "compact", permission: "permission", export: "export" };
30076
+ return paintPicker(icon, commandIcons[name2.toLocaleLowerCase()] ?? "command");
30077
+ }
30000
30078
  if (source === "Skills") return paintPicker(icon, "skill");
30001
30079
  if (source === "Files and folders") return paintPicker(icon, workspacePathIconKind(name2, name2.endsWith("/") ? "directory" : "file"));
30002
30080
  if (source === "DSH sessions") return paintPicker(icon, "session");
package/lib/contract.js CHANGED
@@ -25,7 +25,7 @@ export const driveCandidateSchema = z.object({
25
25
  * lookup back to DSH's native Remotes and removed its last other caller.
26
26
  */
27
27
  const agentLookup = { name: 'agent', wire: 'agentId', source: 'lookup', lookup: 'agent',
28
- codec: { mode: 'strict', typeSymbol: '@deepseek-ai/dsh-session/types#SessionId', schema: z.string().min(1) } };
28
+ codec: { mode: 'strict', typeSymbol: '@deepseek-ai/dsh-session/types#SessionId', schema: z.string().min(1), create: () => z.string().min(1) } };
29
29
  export const searchInputSchema = z.object({
30
30
  query: z.string(), provider: providerSchema.optional(), limit: z.number().int().min(1).max(100),
31
31
  }).readonly();
@@ -159,7 +159,7 @@ export const REFERENCE_ANYTHING_INVOCATIONS = [
159
159
  descriptor('openListReindex', [{ name: 'input', wire: 'input', source: 'json', codec: strict('OpenListMountInput', openListMountInputSchema) }], strict('OpenListReindex', z.object({ supported: z.boolean(), reason: z.string().optional() }).readonly()), true),
160
160
  ];
161
161
  function strict(type, schema) {
162
- return { mode: 'strict', typeSymbol: `dsh-reference-anything#${type}`, schema };
162
+ return { mode: 'strict', typeSymbol: `dsh-reference-anything#${type}`, schema, create: () => schema };
163
163
  }
164
164
  function descriptor(method, parameters, result, cancelled = false) {
165
165
  return {
@@ -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
- if (record['type'] === 'session_info')
87
- named = stringField(record, 'name') ?? named;
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
- const title = bookmark?.title.trim() ?? '';
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
- await walk(root, root.path, 0, adapter, found, signal, maxTranscripts);
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({ kind: root.kind, path, relPath, mtimeMs: stats.mtimeMs, size: stats.size });
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.0",
3
+ "version": "0.4.2",
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": {