dsh-reference-anything 0.2.5 → 0.3.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/lib/wire.d.ts CHANGED
@@ -22,36 +22,47 @@ declare const pickerSourceSettingsSchema: z.ZodObject<{
22
22
  enabled: z.ZodBoolean;
23
23
  order: z.ZodNumber;
24
24
  limit: z.ZodNumber;
25
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
25
26
  }, z.core.$strip>;
26
27
  export type PickerSourceSettings = z.infer<typeof pickerSourceSettingsSchema>;
27
28
  export declare const pickerSettingsSchema: z.ZodObject<{
29
+ displayMode: z.ZodDefault<z.ZodEnum<{
30
+ collapse: "collapse";
31
+ "native-scroll": "native-scroll";
32
+ }>>;
28
33
  commands: z.ZodObject<{
29
34
  enabled: z.ZodBoolean;
30
35
  order: z.ZodNumber;
31
36
  limit: z.ZodNumber;
37
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
32
38
  }, z.core.$strip>;
33
39
  skills: z.ZodObject<{
34
40
  enabled: z.ZodBoolean;
35
41
  order: z.ZodNumber;
36
42
  limit: z.ZodNumber;
43
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
37
44
  }, z.core.$strip>;
38
45
  files: z.ZodObject<{
39
46
  enabled: z.ZodBoolean;
40
47
  order: z.ZodNumber;
41
48
  limit: z.ZodNumber;
49
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
42
50
  }, z.core.$strip>;
43
51
  sessions: z.ZodObject<{
44
52
  enabled: z.ZodBoolean;
45
53
  order: z.ZodNumber;
46
54
  limit: z.ZodNumber;
55
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
47
56
  }, z.core.$strip>;
48
57
  conversations: z.ZodObject<{
49
58
  enabled: z.ZodBoolean;
50
59
  order: z.ZodNumber;
51
60
  limit: z.ZodNumber;
61
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
52
62
  }, z.core.$strip>;
53
63
  }, z.core.$strip>;
54
64
  export type PickerSettings = z.infer<typeof pickerSettingsSchema>;
65
+ export type PickerDisplayMode = PickerSettings['displayMode'];
55
66
  export declare const inputRenderModeSchema: z.ZodEnum<{
56
67
  pill: "pill";
57
68
  "raw-text": "raw-text";
@@ -86,30 +97,39 @@ export declare const settingsRecordSchema: z.ZodObject<{
86
97
  "raw-text": "raw-text";
87
98
  }>>;
88
99
  picker: z.ZodOptional<z.ZodObject<{
100
+ displayMode: z.ZodDefault<z.ZodEnum<{
101
+ collapse: "collapse";
102
+ "native-scroll": "native-scroll";
103
+ }>>;
89
104
  commands: z.ZodObject<{
90
105
  enabled: z.ZodBoolean;
91
106
  order: z.ZodNumber;
92
107
  limit: z.ZodNumber;
108
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
93
109
  }, z.core.$strip>;
94
110
  skills: z.ZodObject<{
95
111
  enabled: z.ZodBoolean;
96
112
  order: z.ZodNumber;
97
113
  limit: z.ZodNumber;
114
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
98
115
  }, z.core.$strip>;
99
116
  files: z.ZodObject<{
100
117
  enabled: z.ZodBoolean;
101
118
  order: z.ZodNumber;
102
119
  limit: z.ZodNumber;
120
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
103
121
  }, z.core.$strip>;
104
122
  sessions: z.ZodObject<{
105
123
  enabled: z.ZodBoolean;
106
124
  order: z.ZodNumber;
107
125
  limit: z.ZodNumber;
126
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
108
127
  }, z.core.$strip>;
109
128
  conversations: z.ZodObject<{
110
129
  enabled: z.ZodBoolean;
111
130
  order: z.ZodNumber;
112
131
  limit: z.ZodNumber;
132
+ maxCandidates: z.ZodDefault<z.ZodNumber>;
113
133
  }, z.core.$strip>;
114
134
  }, z.core.$strip>>;
115
135
  }, z.core.$strip>;
package/lib/wire.js CHANGED
@@ -7,8 +7,10 @@ const pickerSourceSettingsSchema = z.object({
7
7
  enabled: z.boolean(),
8
8
  order: z.number().int().min(0).max(100),
9
9
  limit: z.number().int().min(1).max(50),
10
+ maxCandidates: z.number().int().min(1).max(50).default(50),
10
11
  });
11
12
  export const pickerSettingsSchema = z.object({
13
+ displayMode: z.enum(['collapse', 'native-scroll']).default('collapse'),
12
14
  commands: pickerSourceSettingsSchema,
13
15
  skills: pickerSourceSettingsSchema,
14
16
  files: pickerSourceSettingsSchema,
@@ -19,18 +21,22 @@ export const inputRenderModeSchema = z.enum(['pill', 'raw-text']);
19
21
  /** Defaults used before the user saves the General section. */
20
22
  export function defaultPickerSettings() {
21
23
  return {
22
- commands: { enabled: true, order: 0, limit: 6 },
23
- skills: { enabled: true, order: 5, limit: 6 },
24
- files: { enabled: true, order: 10, limit: 6 },
25
- sessions: { enabled: true, order: 20, limit: 6 },
26
- conversations: { enabled: true, order: 30, limit: 6 },
24
+ displayMode: 'collapse',
25
+ commands: { enabled: true, order: 0, limit: 6, maxCandidates: 50 },
26
+ skills: { enabled: true, order: 5, limit: 6, maxCandidates: 50 },
27
+ files: { enabled: true, order: 10, limit: 6, maxCandidates: 50 },
28
+ sessions: { enabled: true, order: 20, limit: 6, maxCandidates: 50 },
29
+ conversations: { enabled: true, order: 30, limit: 6, maxCandidates: 50 },
27
30
  };
28
31
  }
29
32
  /** Picker settings have a fixed schema, so explicit comparison is cheap and stable. */
30
33
  export function samePickerSettings(left, right) {
31
- return Object.keys(left).every(key => left[key].enabled === right[key].enabled &&
34
+ if (left.displayMode !== right.displayMode)
35
+ return false;
36
+ return pickerSourceSchema.options.every(key => left[key].enabled === right[key].enabled &&
32
37
  left[key].order === right[key].order &&
33
- left[key].limit === right[key].limit);
38
+ left[key].limit === right[key].limit &&
39
+ left[key].maxCandidates === right[key].maxCandidates);
34
40
  }
35
41
  export const settingsRecordSchema = z.object({
36
42
  opencliPath: z.string(),
@@ -49,10 +49,54 @@ function assertResult(result, domain, operation) {
49
49
  return result.rows
50
50
  }
51
51
 
52
+ const PROVIDER_READY_TIMEOUT_MS = 1200
53
+ const PROVIDER_READY_INITIAL_DELAY_MS = 50
54
+ const PROVIDER_READY_MAX_DELAY_MS = 400
55
+
56
+ function terminalProviderResult(result) {
57
+ return result?.ok === true || result?.code === 'AUTH' || result?.code === 'RATE_LIMIT'
58
+ }
59
+
60
+ function terminalIdentityResult(result) {
61
+ return result?.code === 'AUTH' || result?.code === 'RATE_LIMIT'
62
+ || (result?.ok === true && typeof result.identity === 'string' && result.identity.length > 0)
63
+ }
64
+
65
+ /**
66
+ * Run the provider API as the readiness probe. The happy path performs one
67
+ * evaluate immediately after navigation; transient bootstrap failures retry
68
+ * with bounded exponential backoff instead of waiting for the whole SPA DOM
69
+ * to become quiet first.
70
+ */
71
+ export async function evaluateWhenProviderReady(page, script, args, operation, options = {}) {
72
+ const timeoutMs = options.timeoutMs ?? PROVIDER_READY_TIMEOUT_MS
73
+ const maxDelayMs = options.maxDelayMs ?? PROVIDER_READY_MAX_DELAY_MS
74
+ const accepts = options.accepts ?? terminalProviderResult
75
+ const deadline = Date.now() + timeoutMs
76
+ let delayMs = options.initialDelayMs ?? PROVIDER_READY_INITIAL_DELAY_MS
77
+ let lastResult
78
+ let lastError
79
+ for (;;) {
80
+ try {
81
+ lastResult = await evaluate(page, script, args, operation)
82
+ lastError = undefined
83
+ if (accepts(lastResult)) return lastResult
84
+ } catch (error) {
85
+ lastError = error
86
+ }
87
+ const remaining = deadline - Date.now()
88
+ if (remaining <= 0) {
89
+ if (lastError) throw lastError
90
+ return lastResult
91
+ }
92
+ await new Promise(resolve => setTimeout(resolve, Math.min(delayMs, remaining)))
93
+ delayMs = Math.min(Math.max(delayMs * 2, 1), maxDelayMs)
94
+ }
95
+ }
96
+
52
97
  /**
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.
98
+ * Each command owns an ephemeral Browser Bridge tab. Close that tab after the
99
+ * command finishes while preserving the provider result or failure.
56
100
  */
57
101
  async function inTemporaryTab(page, task) {
58
102
  try {
@@ -80,10 +124,12 @@ export function registerProvider(config) {
80
124
  ...browserSession,
81
125
  navigateBefore: false,
82
126
  args: [],
83
- columns: ['identity'],
127
+ columns: ['identity'],
84
128
  func: async (page) => inTemporaryTab(page, async () => {
85
- await page.goto(config.home, { settleMs: 500 })
86
- const result = await evaluate(page, config.whoamiScript, {}, `${config.site} whoami`)
129
+ await page.goto(config.home, { waitUntil: 'none' })
130
+ const result = await evaluateWhenProviderReady(
131
+ page, config.whoamiScript, {}, `${config.site} whoami`, { accepts: terminalIdentityResult },
132
+ )
87
133
  if (result?.code === 'AUTH') throw new AuthRequiredError(config.domain)
88
134
  if (result?.ok !== true || typeof result.identity !== 'string' || !result.identity) {
89
135
  throw new CommandExecutionError(`${config.site} whoami: ${result?.message || 'stable account identity unavailable'}`)
@@ -110,8 +156,10 @@ export function registerProvider(config) {
110
156
  func: async (page, kwargs) => inTemporaryTab(page, async () => {
111
157
  const since = String(kwargs.since || '').trim()
112
158
  if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
113
- await page.goto(config.home, { settleMs: 1200 })
114
- const identity = await evaluate(page, config.whoamiScript, {}, `${config.site} sync-index identity`)
159
+ await page.goto(config.home, { waitUntil: 'none' })
160
+ const identity = await evaluateWhenProviderReady(
161
+ page, config.whoamiScript, {}, `${config.site} sync-index identity`, { accepts: terminalIdentityResult },
162
+ )
115
163
  if (identity?.code === 'AUTH') throw new AuthRequiredError(config.domain)
116
164
  if (identity?.ok !== true || typeof identity.identity !== 'string' || !identity.identity) {
117
165
  throw new CommandExecutionError(`${config.site} sync-index: ${identity?.message || 'stable account identity unavailable'}`)
@@ -147,12 +195,12 @@ export function registerProvider(config) {
147
195
  columns: HISTORY_COLUMNS,
148
196
  func: async (page, kwargs) => inTemporaryTab(page, async () => {
149
197
  const since = String(kwargs.since || '').trim()
150
- if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
151
- await page.goto(config.home, { settleMs: 1200 })
152
- const rows = assertResult(
153
- await evaluate(page, config.historyScript, { since }, `${config.site} history-all`),
154
- config.domain,
155
- `${config.site} history-all`,
198
+ if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
199
+ await page.goto(config.home, { waitUntil: 'none' })
200
+ const rows = assertResult(
201
+ await evaluateWhenProviderReady(page, config.historyScript, { since }, `${config.site} history-all`),
202
+ config.domain,
203
+ `${config.site} history-all`,
156
204
  )
157
205
  return rows
158
206
  }),
@@ -247,16 +295,12 @@ export function registerProvider(config) {
247
295
  localPath: output,
248
296
  }]
249
297
  }),
250
- })
298
+ })
251
299
  }
252
300
 
253
- /**
254
- * Keep one stable, background Browser Bridge session per provider site.
255
- * Persistence suppresses OpenCLI's `close-window` action; inTemporaryTab closes
256
- * only the finished command's tab so the reusable container window stays alive.
257
- */
301
+ /** Browser Bridge lifecycle shared by every web-chat command. */
258
302
  export const SYNC_BROWSER_SESSION = Object.freeze({
259
- siteSession: 'persistent',
303
+ siteSession: 'ephemeral',
260
304
  defaultWindowMode: 'background',
261
305
  })
262
306
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-chat-history",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "opencli": ">=1.8.6",
5
5
  "description": "Full-history read adapters used by dsh-reference-anything"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencli-plugin-dsh-chat-history",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@jackwener/opencli": ">=1.8.6"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-reference-anything",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "Reference locally mirrored ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi conversations from DeepSeek Harness",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -85,30 +85,34 @@
85
85
  "peerDependencies": {
86
86
  "@deepseek-ai/cordis": ">=4.0.1",
87
87
  "@deepseek-ai/dsh-agent": ">=0.1.0-rc.6",
88
- "@deepseek-ai/dsh-api-remotes": ">=0.0.1-rc.1",
88
+ "@deepseek-ai/dsh-api-remotes": ">=0.1.0-rc.8",
89
89
  "@deepseek-ai/dsh-attachment": ">=0.1.0-rc.6",
90
- "@deepseek-ai/dsh-client-connection": ">=0.0.1-rc.1",
91
- "@deepseek-ai/dsh-client-locale": ">=0.0.1-rc.1",
92
- "@deepseek-ai/dsh-client-runtime": ">=0.0.1-rc.1",
93
- "@deepseek-ai/dsh-client-ui-conversation": ">=0.0.1-rc.1",
94
- "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.0.1-rc.3",
95
- "@deepseek-ai/dsh-client-ui-settings": ">=0.0.1-rc.1",
96
- "@deepseek-ai/dsh-client-ui-slots": ">=0.0.1-rc.1",
90
+ "@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.8",
91
+ "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.8",
92
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.8",
93
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.8",
94
+ "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.0-rc.8",
95
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.8",
96
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.8",
97
+ "@deepseek-ai/dsh-file-reference": ">=0.1.0-rc.8",
97
98
  "@deepseek-ai/dsh-llm": ">=0.1.0-rc.6",
98
99
  "@deepseek-ai/dsh-output-retention": ">=0.1.0-rc.6",
99
100
  "@deepseek-ai/dsh-session": ">=0.1.0-rc.6",
100
101
  "@deepseek-ai/dsh-session-reference": ">=0.1.0-rc.6",
101
- "@deepseek-ai/dsh-settings": ">=0.0.1-rc.1",
102
- "@deepseek-ai/dsh-storage-domain": ">=0.0.1-rc.1",
102
+ "@deepseek-ai/dsh-settings": ">=0.1.0-rc.8",
103
+ "@deepseek-ai/dsh-storage-domain": ">=0.1.0-rc.8",
103
104
  "@deepseek-ai/dsh-tools": ">=0.1.0-rc.6",
104
105
  "@deepseek-ai/dsh-typert-protocol": ">=0.1.0-rc.6",
105
- "@deepseek-ai/dsh-typert-registry": ">=0.0.1-rc.1",
106
+ "@deepseek-ai/dsh-typert-registry": ">=0.1.0-rc.8",
106
107
  "react": "^18.2.0"
107
108
  },
108
109
  "peerDependenciesMeta": {
109
110
  "@deepseek-ai/dsh-session-reference": {
110
111
  "optional": true
111
112
  },
113
+ "@deepseek-ai/dsh-file-reference": {
114
+ "optional": true
115
+ },
112
116
  "@deepseek-ai/dsh-client-runtime": {
113
117
  "optional": true
114
118
  },
@@ -157,6 +161,7 @@
157
161
  "@deepseek-ai/dsh-client-ui-input-trigger": "link:../deepseek-harness/packages/client/ui-input-trigger",
158
162
  "@deepseek-ai/dsh-client-ui-settings": "link:../deepseek-harness/packages/client/ui-settings",
159
163
  "@deepseek-ai/dsh-client-ui-slots": "link:../deepseek-harness/packages/client/ui-slots",
164
+ "@deepseek-ai/dsh-file-reference": "link:../deepseek-harness/packages/context/file-reference",
160
165
  "@deepseek-ai/dsh-llm": "link:../deepseek-harness/packages/llm/llm",
161
166
  "@deepseek-ai/dsh-output-retention": "link:../deepseek-harness/packages/util/output-retention",
162
167
  "@deepseek-ai/dsh-session": "link:../deepseek-harness/packages/core/session",
@@ -1,18 +0,0 @@
1
- import type { Agent } from '@deepseek-ai/dsh-agent';
2
- export declare const WORKSPACE_REFERENCE_SCHEME = "dsh-file:";
3
- export interface WorkspaceEntry {
4
- path: string;
5
- kind: 'file' | 'directory';
6
- }
7
- export interface WorkspaceReference {
8
- path: string;
9
- label: string;
10
- }
11
- export declare function indexWorkspace(agent: Agent, signal: AbortSignal, maxFiles?: number): Promise<WorkspaceEntry[]>;
12
- export declare function encodeWorkspaceReferenceUri(path: string): string;
13
- export declare function formatWorkspaceMention(reference: WorkspaceReference): string;
14
- export declare function parseWorkspaceReferenceText(text: string): {
15
- text: string;
16
- references: WorkspaceReference[];
17
- };
18
- export declare function renderWorkspaceReferences(agent: Agent, references: readonly WorkspaceReference[]): Promise<string>;
package/lib/workspace.js DELETED
@@ -1,81 +0,0 @@
1
- import { lstat, readdir } from 'node:fs/promises';
2
- import { basename, isAbsolute, relative, resolve, sep } from 'node:path';
3
- export const WORKSPACE_REFERENCE_SCHEME = 'dsh-file:';
4
- const IGNORED = new Set(['.git', '.hg', '.svn', 'node_modules', 'dist', 'build', 'coverage', '.next', '.cache', '.idea', '.vscode', '__pycache__']);
5
- const IGNORED_FILES = new Set(['desktop.ini', 'Thumbs.db', '.DS_Store']);
6
- export async function indexWorkspace(agent, signal, maxFiles = 5000) {
7
- const root = agent.session.header.cwd;
8
- if (!root || !isAbsolute(root))
9
- return [];
10
- const output = [];
11
- const queue = ['.'];
12
- while (queue.length > 0 && output.length < maxFiles) {
13
- signal.throwIfAborted();
14
- const current = queue.shift();
15
- let entries;
16
- try {
17
- entries = await readdir(resolve(root, current), { withFileTypes: true });
18
- }
19
- catch {
20
- continue;
21
- }
22
- entries.sort((a, b) => a.name.localeCompare(b.name));
23
- for (const entry of entries) {
24
- if (output.length >= maxFiles)
25
- break;
26
- if (entry.isSymbolicLink() || IGNORED_FILES.has(entry.name))
27
- continue;
28
- const path = current === '.' ? entry.name : `${current}/${entry.name}`;
29
- if (entry.isDirectory()) {
30
- if (IGNORED.has(entry.name))
31
- continue;
32
- output.push({ path, kind: 'directory' });
33
- queue.push(path);
34
- }
35
- else if (entry.isFile())
36
- output.push({ path, kind: 'file' });
37
- }
38
- }
39
- return output;
40
- }
41
- export function encodeWorkspaceReferenceUri(path) {
42
- return `${WORKSPACE_REFERENCE_SCHEME}${Buffer.from(JSON.stringify(path), 'utf8').toString('base64url')}`;
43
- }
44
- export function formatWorkspaceMention(reference) {
45
- return `@[${reference.label.replace(/[\\\]]/gu, value => `\\${value}`)}](${encodeWorkspaceReferenceUri(reference.path)})`;
46
- }
47
- export function parseWorkspaceReferenceText(text) {
48
- const references = [];
49
- const pattern = /@\[((?:\\.|[^\\\]])*)\]\((dsh-file:[^\s)]*)\)|(dsh-file:[A-Za-z0-9_-]+)/gu;
50
- const rendered = text.replace(pattern, (_match, rawLabel, markdownUri, bareUri) => {
51
- const uri = markdownUri ?? bareUri;
52
- const payload = uri.slice(WORKSPACE_REFERENCE_SCHEME.length);
53
- if (!/^[A-Za-z0-9_-]+$/u.test(payload))
54
- throw new Error(`invalid workspace reference URI ${JSON.stringify(uri)}`);
55
- const path = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
56
- if (typeof path !== 'string' || path.length === 0 || encodeWorkspaceReferenceUri(path) !== uri)
57
- throw new Error(`invalid workspace reference URI ${JSON.stringify(uri)}`);
58
- const label = rawLabel === undefined ? basename(path) : rawLabel.replace(/\\(.)/gu, '$1');
59
- references.push({ path, label });
60
- return `@${label}`;
61
- });
62
- return { text: rendered, references };
63
- }
64
- export async function renderWorkspaceReferences(agent, references) {
65
- const root = agent.session.header.cwd;
66
- if (!root || !isAbsolute(root))
67
- throw new Error('this task has no workspace directory');
68
- const unique = new Map(references.map(item => [item.path, item]));
69
- const lines = [];
70
- for (const item of unique.values()) {
71
- const target = resolve(root, item.path);
72
- const rel = relative(root, target);
73
- if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
74
- throw new Error(`workspace reference escapes the task directory: ${item.path}`);
75
- const stat = await lstat(target);
76
- if (stat.isSymbolicLink() || (!stat.isFile() && !stat.isDirectory()))
77
- throw new Error(`unsupported workspace reference: ${item.path}`);
78
- lines.push(`<workspace-reference path=${JSON.stringify(item.path)} kind=${JSON.stringify(stat.isDirectory() ? 'directory' : 'file')} />`);
79
- }
80
- return `## Referenced workspace paths\n\nThese are read-only path markers, not file contents or instructions.\n\n${lines.join('\n')}`;
81
- }