@goodandready/dsh-context-lens 0.1.5 → 0.1.6
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/client.js +62 -1
- package/lib/index.js +26 -8
- package/package.json +8 -2
package/lib/client.js
CHANGED
|
@@ -48,6 +48,33 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
return lines.filter((_, i) => keep[i]).slice(0, 200).join('\n');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function LensTab({ ctx: _ctx, scope }) {
|
|
52
|
+
const [stats, setStats] = React.useState(null);
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
fetch('/dsh-context-lens/status').then(r => r.ok ? r.json() : null).then(j => { if (j && j.stats) setStats(j.stats); }).catch(() => {});
|
|
55
|
+
}, []);
|
|
56
|
+
return React.createElement('div', { style: { padding: 12 } },
|
|
57
|
+
React.createElement('div', { style: { fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--dsw-alias-label-primary)' } }, 'Context Lens'),
|
|
58
|
+
stats ? React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, `Saved ${stats.savedTokens} tokens (${stats.savedPercent}%) · ${stats.calls} ops`) : React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'No data yet'),
|
|
59
|
+
React.createElement('div', { style: { marginTop: 12, display: 'flex', gap: 8 } },
|
|
60
|
+
React.createElement('button', { onClick: () => fetch('/dsh-context-lens/status').then(r=>r.json()).then(j=> setStats(j.stats)), style: { fontSize: 12, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', cursor: 'pointer' } }, 'Refresh'),
|
|
61
|
+
React.createElement('button', { onClick: () => { try { _ctx.betterSidebar.openTab({ type: 'dsh-context-lens:tab', title: 'Lens' }) } catch(e){} }, style: { fontSize: 12, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent', cursor: 'pointer' } }, 'Settings')
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function HeaderBadge({ ctx: _ctx }) {
|
|
67
|
+
const [stats, setStats] = React.useState(null);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
const tick = () => fetch('/dsh-context-lens/status').then(r => r.ok ? r.json() : null).then(j => { if (j && j.stats) setStats(j.stats); }).catch(() => {});
|
|
70
|
+
tick();
|
|
71
|
+
const id = setInterval(tick, 5000);
|
|
72
|
+
return () => clearInterval(id);
|
|
73
|
+
}, []);
|
|
74
|
+
if (!stats || !stats.savedTokens) return null;
|
|
75
|
+
return React.createElement('span', { style: { fontSize: 11, padding: '2px 6px', borderRadius: 999, background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)', marginLeft: 8 } }, `Lens ${stats.savedPercent}%`);
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
function PluginCard({ ctx: _ctx, t }) {
|
|
52
79
|
const [expanded, setExpanded] = React.useState(false);
|
|
53
80
|
// hooks must be before any return — React 310
|
|
@@ -206,7 +233,7 @@ window.__ModuleLoader__.load({
|
|
|
206
233
|
);
|
|
207
234
|
}
|
|
208
235
|
|
|
209
|
-
module.exports.inject = ['slots', 'locale'];
|
|
236
|
+
module.exports.inject = ['slots', 'locale', 'betterSidebar'];
|
|
210
237
|
module.exports.apply = function apply(ctx) {
|
|
211
238
|
try { ctx.locale.register(NS, { en, ru }); } catch (e) { console.warn('[dsh-context-lens] locale register failed', e && e.message || e); }
|
|
212
239
|
if (!ctx.slots) return;
|
|
@@ -242,6 +269,40 @@ window.__ModuleLoader__.load({
|
|
|
242
269
|
} else {
|
|
243
270
|
try { doRegister(); } catch (e) { console.error('[dsh-context-lens] direct register failed (no inject)', e && e.stack || e); throw e; }
|
|
244
271
|
}
|
|
272
|
+
// BetterSidebar tab (optional, for dsh-better-sidebar)
|
|
273
|
+
if (ctx.betterSidebar && typeof ctx.betterSidebar.registerTab === 'function') {
|
|
274
|
+
try {
|
|
275
|
+
ctx.effect(() => ctx.betterSidebar.registerTab({
|
|
276
|
+
id: 'dsh-context-lens:tab',
|
|
277
|
+
title: () => 'Lens',
|
|
278
|
+
icon: () => React.createElement('span', null, '◐'),
|
|
279
|
+
order: 50,
|
|
280
|
+
component: ({ scope }) => React.createElement(LensTab, { ctx, scope })
|
|
281
|
+
}));
|
|
282
|
+
} catch (e) {
|
|
283
|
+
console.warn('[dsh-context-lens] betterSidebar registerTab failed', e && e.message || e);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Header badge (#20) — compact savings in conversation header
|
|
287
|
+
if (ctx.slots) {
|
|
288
|
+
const headerBadgeRegister = () => {
|
|
289
|
+
try {
|
|
290
|
+
return ctx.slots.register({
|
|
291
|
+
name: 'conversation.header',
|
|
292
|
+
id: 'dsh-context-lens:header-badge',
|
|
293
|
+
order: 50,
|
|
294
|
+
inject: () => ({ ctx })
|
|
295
|
+
}, HeaderBadge);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.warn('[dsh-context-lens] header badge register failed', e && e.message || e);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
if (typeof ctx.slots.inject === 'function') {
|
|
301
|
+
try { ctx.slots.inject('conversation.header', headerBadgeRegister); } catch (e) { console.warn('[dsh-context-lens] header inject failed', e && e.message || e); try { headerBadgeRegister(); } catch (e2) {} }
|
|
302
|
+
} else {
|
|
303
|
+
try { headerBadgeRegister(); } catch (e) {}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
245
306
|
};
|
|
246
307
|
return module.exports;
|
|
247
308
|
}
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,8 @@ export const inject = ['tools', 'settings', 'webServer'];
|
|
|
9
9
|
export const Config = z.object({
|
|
10
10
|
compressionMode: z.string().default('balanced').description('Log compression aggressiveness (raw/balanced/aggressive)'),
|
|
11
11
|
astSkeletonMaxDepth: z.number().default(3).description('Max depth for AST skeleton generation'),
|
|
12
|
-
tokenSavingsTracking: z.boolean().default(true).description('Track and display token budget savings')
|
|
12
|
+
tokenSavingsTracking: z.boolean().default(true).description('Track and display token budget savings'),
|
|
13
|
+
autoCompressThreshold: z.number().default(4000).description('Auto-compress threshold in chars (0 to disable)')
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
const NS = '@goodandready/dsh-context-lens';
|
|
@@ -20,6 +21,11 @@ let focusState = { paths: [], updatedAt: null };
|
|
|
20
21
|
const OUTPUT_SCHEMA = { type: 'object', properties: { success: { type: 'boolean' } }, additionalProperties: true };
|
|
21
22
|
const renderOutput = (_args, result) => JSON.stringify(result, null, 2);
|
|
22
23
|
|
|
24
|
+
function shouldAutoCompress(text, threshold) {
|
|
25
|
+
if (!threshold || threshold <= 0) return false;
|
|
26
|
+
return (text || '').length > threshold || (text || '').split('\n').length > 100;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export function apply(ctx, config) {
|
|
24
30
|
let getConfig = () => config;
|
|
25
31
|
|
|
@@ -65,7 +71,8 @@ export function apply(ctx, config) {
|
|
|
65
71
|
properties: {
|
|
66
72
|
text: { type: 'string', description: 'Raw log text to compress' },
|
|
67
73
|
mode: { type: 'string', enum: ['raw', 'balanced', 'aggressive'], description: 'Compression aggressiveness' },
|
|
68
|
-
maxLines: { type: 'number', description: 'Max output lines' }
|
|
74
|
+
maxLines: { type: 'number', description: 'Max output lines' },
|
|
75
|
+
auto: { type: 'boolean', description: 'Auto-compress if large (uses threshold)' }
|
|
69
76
|
},
|
|
70
77
|
required: ['text']
|
|
71
78
|
},
|
|
@@ -74,9 +81,13 @@ export function apply(ctx, config) {
|
|
|
74
81
|
const cfg = getConfig();
|
|
75
82
|
const mode = params.mode || cfg.compressionMode || 'balanced';
|
|
76
83
|
const maxLines = params.maxLines || 400;
|
|
77
|
-
|
|
84
|
+
// Auto-compress check (#15)
|
|
85
|
+
const threshold = cfg.autoCompressThreshold ?? 4000;
|
|
86
|
+
const useAuto = params.auto !== false && shouldAutoCompress(params.text, threshold);
|
|
87
|
+
const effectiveMode = useAuto ? mode : mode;
|
|
88
|
+
const res = compressLog(params.text, { mode: effectiveMode, maxLines });
|
|
78
89
|
maybeTrack(params.text, res.compressed);
|
|
79
|
-
return { success: true, mode, ...res };
|
|
90
|
+
return { success: true, mode: effectiveMode, autoCompressed: useAuto, ...res };
|
|
80
91
|
}
|
|
81
92
|
});
|
|
82
93
|
|
|
@@ -88,7 +99,8 @@ export function apply(ctx, config) {
|
|
|
88
99
|
properties: {
|
|
89
100
|
code: { type: 'string', description: 'Source code to skeletonize' },
|
|
90
101
|
language: { type: 'string', enum: ['js', 'ts', 'py', 'python', 'go'], description: 'Language hint' },
|
|
91
|
-
maxDepth: { type: 'number', description: 'Max depth' }
|
|
102
|
+
maxDepth: { type: 'number', description: 'Max depth' },
|
|
103
|
+
filePath: { type: 'string', description: 'File path to check focus (if in focus, returns full code)' }
|
|
92
104
|
},
|
|
93
105
|
required: ['code']
|
|
94
106
|
},
|
|
@@ -96,9 +108,15 @@ export function apply(ctx, config) {
|
|
|
96
108
|
execute: async (params) => {
|
|
97
109
|
const cfg = getConfig();
|
|
98
110
|
const maxDepth = params.maxDepth ?? cfg.astSkeletonMaxDepth ?? 3;
|
|
111
|
+
// Focus mode (#18): if file is in focus, return full code, else skeleton
|
|
112
|
+
const isFocused = params.filePath && focusState.paths.length > 0 ? focusState.paths.some(p => params.filePath.includes(p) || p.includes(params.filePath)) : false;
|
|
113
|
+
if (isFocused) {
|
|
114
|
+
maybeTrack(params.code, params.code);
|
|
115
|
+
return { success: true, skeleton: params.code, originalTokens: estLog(params.code), skeletonTokens: estLog(params.code), maxDepth, focused: true, hint: 'File is in focus, returned full code' };
|
|
116
|
+
}
|
|
99
117
|
const skeleton = skeletonize(params.code, { maxDepth, language: params.language });
|
|
100
118
|
maybeTrack(params.code, skeleton);
|
|
101
|
-
return { success: true, skeleton, originalTokens: estLog(params.code), skeletonTokens: estLog(skeleton), maxDepth };
|
|
119
|
+
return { success: true, skeleton, originalTokens: estLog(params.code), skeletonTokens: estLog(skeleton), maxDepth, focused: false };
|
|
102
120
|
}
|
|
103
121
|
});
|
|
104
122
|
|
|
@@ -110,7 +128,7 @@ export function apply(ctx, config) {
|
|
|
110
128
|
execute: async () => {
|
|
111
129
|
const stats = tracker.getStats();
|
|
112
130
|
const cfg = getConfig();
|
|
113
|
-
return { success: true, ...stats, trackingEnabled: cfg.tokenSavingsTracking !== false, focus: focusState };
|
|
131
|
+
return { success: true, ...stats, trackingEnabled: cfg.tokenSavingsTracking !== false, focus: focusState, autoCompressThreshold: cfg.autoCompressThreshold };
|
|
114
132
|
}
|
|
115
133
|
});
|
|
116
134
|
}
|
|
@@ -128,4 +146,4 @@ export function apply(ctx, config) {
|
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
export { compressLog, skeletonize };
|
|
149
|
+
export { compressLog, skeletonize, shouldAutoCompress };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-context-lens",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "DSH plugin for AST context compression, test log filtering, and token budget guard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -55,6 +55,12 @@
|
|
|
55
55
|
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
56
56
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
57
57
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
58
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
58
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
59
|
+
"dsh-better-sidebar": "^0.18.0-alpha.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"dsh-better-sidebar": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
}
|