@goodandready/dsh-context-lens 0.1.16 → 0.1.17
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 +11 -5
- package/README.ru.md +11 -5
- package/README.zh.md +11 -1
- package/lib/ast/skeletonizer.js +31 -15
- package/lib/client.js +106 -32
- package/lib/index.js +36 -8
- package/lib/tokens/tracker.js +14 -6
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
|
+
<table align="center">
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center">
|
|
28
|
+
⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
|
|
29
|
+
<br><br>
|
|
30
|
+
🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
</table>
|
|
34
|
+
|
|
25
35
|
</div>
|
|
26
36
|
|
|
27
37
|
---
|
|
@@ -32,7 +42,7 @@
|
|
|
32
42
|
|
|
33
43
|
Large context windows are expensive, prone to model distraction, and vulnerable to rate limits. When agents inspect multi-file codebases or run bulky test suites, thousands of tokens are wasted on boilerplate function bodies, passing test logs, and build artifacts.
|
|
34
44
|
|
|
35
|
-
`dsh-context-lens` introduces **active path focusing, AST structural code skeletonization (JS/TS/Python/Go), and fast O(n) heuristic log compression**, shrinking context consumption by **up to 85%** while keeping 100% of essential architectural interfaces and failure traces.
|
|
45
|
+
`dsh-context-lens` introduces **active path focusing, AST structural code skeletonization (JS/TS/Python/Go/Rust/Java/C/C++/SQL), and fast O(n) heuristic log compression**, shrinking context consumption by **up to 85%** while keeping 100% of essential architectural interfaces and failure traces.
|
|
36
46
|
|
|
37
47
|
```mermaid
|
|
38
48
|
graph LR
|
|
@@ -148,7 +158,6 @@ dsh-context-lens:
|
|
|
148
158
|
|
|
149
159
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
150
160
|
|
|
151
|
-
|
|
152
161
|
## Changed in v0.1.11
|
|
153
162
|
|
|
154
163
|
Audit fixes (#33–#47, #18):
|
|
@@ -161,19 +170,16 @@ Audit fixes (#33–#47, #18):
|
|
|
161
170
|
- Python imports kept in skeletons; Java locals no longer mistaken for signatures.
|
|
162
171
|
- Shared `estimateTokens` helper; unused `dsh-credentials` peer removed.
|
|
163
172
|
|
|
164
|
-
|
|
165
173
|
## Changed in v0.1.12
|
|
166
174
|
|
|
167
175
|
#43: extract shared `StatusPanel` used by LensTab and HeaderChip popover (budget bar, history, refresh).
|
|
168
176
|
|
|
169
|
-
|
|
170
177
|
## Changed in v0.1.13
|
|
171
178
|
|
|
172
179
|
- **Fix (#50)**: Guard optional `betterSidebar` tab registration via `ctx.inject(['betterSidebar'], ...)` instead of direct property access on Cordis context proxy, resolving `cannot get property "betterSidebar" without inject` on client boot.
|
|
173
180
|
- **Fix (#50)**: Safely guard `_ctx.settingsScope` access in `PluginCard` with try/catch to prevent proxy property errors.
|
|
174
181
|
- **Test**: Added regression test suite simulating strict Cordis Context Proxy behavior.
|
|
175
182
|
|
|
176
|
-
|
|
177
183
|
## Changed in v0.1.15
|
|
178
184
|
|
|
179
185
|
- **Feature (#54)**: Support native DSH right sidebar (`ctx.sidebarRightTabs` + `sidebar.right.pane.tab` slot) introduced in DSH 0.1.5-alpha.1.
|
package/README.ru.md
CHANGED
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
|
+
<table align="center">
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center">
|
|
28
|
+
⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
|
|
29
|
+
<br><br>
|
|
30
|
+
🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
</table>
|
|
34
|
+
|
|
25
35
|
</div>
|
|
26
36
|
|
|
27
37
|
---
|
|
@@ -32,7 +42,7 @@
|
|
|
32
42
|
|
|
33
43
|
Большие объёмы контекста приводят к высоким затратам токенов, замедлению генерации и быстрой перегрузке лимитов. При изучении крупных репозиториев или прогоне тестов тысячи токенов тратятся на однотипные тела функций, логи успешных тестов и сборку.
|
|
34
44
|
|
|
35
|
-
Плагин внедряет **фокусировку на активных файлах, AST-скелетонизацию исходного кода (JS/TS/Python/Go) и сверхбыструю эвристическую компрессию логов**, снижая расход контекста **до 85%** при сохранении 100% сигнатур типов, интерфейсов и сообщений об ошибках.
|
|
45
|
+
Плагин внедряет **фокусировку на активных файлах, AST-скелетонизацию исходного кода (JS/TS/Python/Go/Rust/Java/C/C++/SQL) и сверхбыструю эвристическую компрессию логов**, снижая расход контекста **до 85%** при сохранении 100% сигнатур типов, интерфейсов и сообщений об ошибках.
|
|
36
46
|
|
|
37
47
|
```mermaid
|
|
38
48
|
graph LR
|
|
@@ -145,24 +155,20 @@ dsh-context-lens:
|
|
|
145
155
|
|
|
146
156
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
147
157
|
|
|
148
|
-
|
|
149
158
|
## Changed in v0.1.11
|
|
150
159
|
|
|
151
160
|
Исправления audit #33–#47 / #18: режим auto-compress не форсирует balanced; budgetLimit останавливает подсчёт; focus per-session; поля settings card; optional betterSidebar; серверный preview; Python imports / Java locals; общий estimateTokens; удалён unused peer dsh-credentials.
|
|
152
161
|
|
|
153
|
-
|
|
154
162
|
## Changed in v0.1.12
|
|
155
163
|
|
|
156
164
|
#43: extract shared `StatusPanel` used by LensTab and HeaderChip popover (budget bar, history, refresh).
|
|
157
165
|
|
|
158
|
-
|
|
159
166
|
## Changed in v0.1.13
|
|
160
167
|
|
|
161
168
|
- **Fix (#50)**: Безопасная регистрация вкладки BetterSidebar через `ctx.inject(['betterSidebar'], ...)` вместо прямого чтения свойства из Cordis Context Proxy. Устранена ошибка `cannot get property "betterSidebar" without inject` при загрузке клиентской половины в DSH.
|
|
162
169
|
- **Fix (#50)**: Безопасное обращение к `_ctx.settingsScope` в `PluginCard` через try/catch.
|
|
163
170
|
- **Тесты**: Добавлены регрессионные тесты со строгим Cordis Context Proxy.
|
|
164
171
|
|
|
165
|
-
|
|
166
172
|
## Changed in v0.1.15
|
|
167
173
|
|
|
168
174
|
- **Функциональность (#54)**: Поддержка встроенной правой боковой панели DSH (`ctx.sidebarRightTabs` + слот `sidebar.right.pane.tab`) из DSH 0.1.5-alpha.1.
|
package/README.zh.md
CHANGED
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
|
+
<table align="center">
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center">
|
|
28
|
+
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
29
|
+
<br><br>
|
|
30
|
+
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
</table>
|
|
34
|
+
|
|
25
35
|
</div>
|
|
26
36
|
|
|
27
37
|
---
|
|
@@ -30,7 +40,7 @@
|
|
|
30
40
|
|
|
31
41
|
**`dsh-context-lens`** 为 **DeepSeek Harness** 智能体提供深度上下文窗口与 Token 预算优化。
|
|
32
42
|
|
|
33
|
-
超长上下文不仅消耗高昂 Token 成本,还会导致模型注意力涣散并频繁触发限流。本插件通过**工作区文件焦点聚焦、跨语言 AST 结构骨架提取(支持 JS/TS/Python/Go)以及 $O(n)$ 终端测试日志启发式精简**,在保留 100% 架构接口与错误堆栈的前提下,将上下文体积削减**高达 85%**。
|
|
43
|
+
超长上下文不仅消耗高昂 Token 成本,还会导致模型注意力涣散并频繁触发限流。本插件通过**工作区文件焦点聚焦、跨语言 AST 结构骨架提取(支持 JS/TS/Python/Go/Rust/Java/C/C++/SQL)以及 $O(n)$ 终端测试日志启发式精简**,在保留 100% 架构接口与错误堆栈的前提下,将上下文体积削减**高达 85%**。
|
|
34
44
|
|
|
35
45
|
```mermaid
|
|
36
46
|
graph LR
|
package/lib/ast/skeletonizer.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
// ponytail: regex skeletons, no tree-sitter — handles JS/TS/Python/Go/Rust/Java signatures + imports/comments + multiline signatures
|
|
1
|
+
// ponytail: regex skeletons, no tree-sitter — handles JS/TS/Python/Go/Rust/Java/C/C++/SQL signatures + imports/comments + multiline signatures
|
|
2
2
|
const JS_FUNC_RE = /^\s*(export\s+)?(async\s+)?(function\s+(\w+)|const\s+(\w+)\s*=\s*(async\s+)?\([^)]*\)\s*=>|(\w+)\s*:\s*\([^)]*\)\s*=>|class\s+(\w+)|interface\s+(\w+)|type\s+(\w+)\s*=)/;
|
|
3
3
|
const PY_RE = /^\s*(def\s+(\w+)\s*\([^)]*\)|class\s+(\w+).*?:|async def\s+(\w+)\s*\([^)]*\))/;
|
|
4
4
|
const GO_RE = /^\s*(func\s+(\([^)]+\)\s+)?(\w+)\s*\([^)]*\)|type\s+(\w+)\s+(struct|interface))/;
|
|
5
5
|
const RUST_RE = /^\s*(pub(\([^)]+\))?\s+)?(async\s+)?(fn|struct|enum|impl|trait|type|const|static)\s+\w+/;
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const CPP_CLASS_RE = /^\s*(?:class|struct)\s+\w+(?:\s*:\s*(?:public|protected|private)\s+\w+)?/;
|
|
7
|
+
const CPP_FUNC_RE = /^\s*(?:(?:virtual|inline|static|explicit|constexpr|friend)\s+)*(?:[\w:<>&*~]+\s+)+[\w:~]+\s*\([^)]*\)(?:\s*const)?(?:\s*override|\s*noexcept)?(?:\s*=\s*0)?/;
|
|
8
|
+
const SQL_RE = /^\s*(?:CREATE|ALTER)\s+(?:TABLE|VIEW|INDEX|TRIGGER|FUNCTION|PROCEDURE)\s+(?:IF\s+NOT\s+EXISTS\s+)?[\w."`]+|^\s*CONSTRAINT\s+[\w."`]+|^\s*FOREIGN\s+KEY|^\s*PRIMARY\s+KEY/i;
|
|
9
|
+
|
|
10
|
+
const IMPORT_RE = /^\s*#include\s+[<"][^>"]+[>"]|^\s*import\s+.*from\s+['"].*['"]|^\s*import\s+['"].*['"]|^\s*export\s+.*from\s+['"]|^\s*use\s+[a-zA-Z0-9_:]+|^\s*(from\s+\S+\s+)?import\s+\S+|^\s*from\s+\S+\s+import\s+/;
|
|
11
|
+
const COMMENT_RE = /^\s*(\/\/.*|\/\*.*\*\/|\/\*.*|\*.*|#.*|--.*)/;
|
|
8
12
|
|
|
9
13
|
// Multiline signature starters
|
|
10
|
-
const MULTILINE_START_RE = /^\s*(export\s+)?(async\s+)?(function\b|const\s+\w+\s*=|\w+\s*:\s*(async\s+)?\(|def\b|async\s+def\b|func\b|(pub(\([^)]+\))?\s+)?(async\s+)?fn\b|(?:(?:public|protected|private|static|final|abstract|synchronized|native|default)\s+)+[\w.<>,\[\]?]+\s+\w+\s*\()/;
|
|
14
|
+
const MULTILINE_START_RE = /^\s*(export\s+)?(async\s+)?(function\b|const\s+\w+\s*=|\w+\s*:\s*(async\s+)?\(|def\b|async\s+def\b|func\b|(pub(\([^)]+\))?\s+)?(async\s+)?fn\b|(?:(?:public|protected|private|static|final|abstract|synchronized|native|default)\s+)+[\w.<>,\[\]?]+\s+\w+\s*\(|(?:(?:virtual|inline|static|explicit|constexpr|friend)\s+)*(?:[\w:<>&*~]+\s+)+[\w:~]+\s*\()/;
|
|
11
15
|
|
|
12
16
|
function indentDepth(line) {
|
|
13
17
|
const m = line.match(/^(\s*)/);
|
|
@@ -28,7 +32,7 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
28
32
|
const d = indentDepth(raw);
|
|
29
33
|
if (d > maxDepth) { pendingComment = null; continue; }
|
|
30
34
|
|
|
31
|
-
// Handle imports
|
|
35
|
+
// Handle imports & includes
|
|
32
36
|
if (includeImports && IMPORT_RE.test(line)) {
|
|
33
37
|
const sig = line.trim().slice(0, 120);
|
|
34
38
|
if (!seen.has(sig)) {
|
|
@@ -39,19 +43,19 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
// Handle comments (keep
|
|
46
|
+
// Handle comments (keep doc or line comments directly above a definition)
|
|
43
47
|
if (includeComments && COMMENT_RE.test(line)) {
|
|
44
48
|
let nextIdx = idx + 1;
|
|
45
49
|
while (nextIdx < lines.length && !lines[nextIdx].trim()) nextIdx++;
|
|
46
50
|
if (nextIdx < lines.length) {
|
|
47
51
|
const nextLine = lines[nextIdx];
|
|
48
|
-
if (JS_FUNC_RE.test(nextLine) || PY_RE.test(nextLine) || GO_RE.test(nextLine) || RUST_RE.test(nextLine) || MULTILINE_START_RE.test(nextLine) || /^\s*(class|function|interface|type|def |func |pub |fn )/.test(nextLine)) {
|
|
52
|
+
if (JS_FUNC_RE.test(nextLine) || PY_RE.test(nextLine) || GO_RE.test(nextLine) || RUST_RE.test(nextLine) || CPP_CLASS_RE.test(nextLine) || CPP_FUNC_RE.test(nextLine) || SQL_RE.test(nextLine) || MULTILINE_START_RE.test(nextLine) || /^\s*(class|function|interface|type|def |func |pub |fn )/.test(nextLine)) {
|
|
49
53
|
pendingComment = line.trim().slice(0, 120);
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
// Otherwise, keep top-level comments (up to 5)
|
|
54
|
-
if (out.length < 5 && (line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('/*'))) {
|
|
58
|
+
if (out.length < 5 && (line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('/*') || line.trim().startsWith('--'))) {
|
|
55
59
|
const sig = line.trim().slice(0, 120);
|
|
56
60
|
if (!seen.has(sig)) {
|
|
57
61
|
seen.add(sig);
|
|
@@ -65,7 +69,7 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
65
69
|
// Multiline signature detection: if line starts a signature but doesn't finish with '{', '=>', or ':'
|
|
66
70
|
let candidateLine = line;
|
|
67
71
|
let accumulatedIdx = idx;
|
|
68
|
-
const isComplete = (s) => (s.includes('{') && s.includes(')')) || (s.includes('=>') && s.includes(')')) || (s.endsWith(':') && s.includes(')'));
|
|
72
|
+
const isComplete = (s) => (s.includes('{') && s.includes(')')) || (s.includes('=>') && s.includes(')')) || (s.endsWith(':') && s.includes(')')) || (s.includes(';') && s.includes(')'));
|
|
69
73
|
if (MULTILINE_START_RE.test(candidateLine) && !isComplete(candidateLine)) {
|
|
70
74
|
let buffer = candidateLine.trim();
|
|
71
75
|
let scanIdx = idx + 1;
|
|
@@ -73,7 +77,7 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
73
77
|
const nextRaw = lines[scanIdx].trim();
|
|
74
78
|
if (!nextRaw) { scanIdx++; continue; }
|
|
75
79
|
buffer += ' ' + nextRaw;
|
|
76
|
-
if ((nextRaw.includes('{') || nextRaw.includes('=>') || nextRaw.endsWith(':')) && buffer.includes(')')) {
|
|
80
|
+
if ((nextRaw.includes('{') || nextRaw.includes('=>') || nextRaw.endsWith(':') || nextRaw.includes(';')) && buffer.includes(')')) {
|
|
77
81
|
candidateLine = buffer;
|
|
78
82
|
accumulatedIdx = scanIdx;
|
|
79
83
|
break;
|
|
@@ -107,9 +111,21 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
107
111
|
|| /^\s*(?:(?:public|protected|private|static|final|abstract|synchronized|native|default)\s+)+[\w.<>,\[\]?]+\s+\w+\s*\(/.test(candidateLine)
|
|
108
112
|
|| /^\s*(class|interface|enum|record)\s+\w+/.test(candidateLine)) sig = candidateLine.trim();
|
|
109
113
|
}
|
|
114
|
+
if (!sig && (!language || language === 'cpp' || language === 'c')) {
|
|
115
|
+
if (CPP_CLASS_RE.test(candidateLine) || CPP_FUNC_RE.test(candidateLine)) {
|
|
116
|
+
sig = candidateLine.trim();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!sig && (!language || language === 'sql')) {
|
|
120
|
+
if (SQL_RE.test(candidateLine)) {
|
|
121
|
+
sig = candidateLine.trim();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
110
124
|
// generic fallback: if no specific language, try all
|
|
111
125
|
if (!sig && !language) {
|
|
112
|
-
if (JS_FUNC_RE.test(candidateLine) || PY_RE.test(candidateLine) || GO_RE.test(candidateLine) || RUST_RE.test(candidateLine))
|
|
126
|
+
if (JS_FUNC_RE.test(candidateLine) || PY_RE.test(candidateLine) || GO_RE.test(candidateLine) || RUST_RE.test(candidateLine) || CPP_CLASS_RE.test(candidateLine) || CPP_FUNC_RE.test(candidateLine) || SQL_RE.test(candidateLine)) {
|
|
127
|
+
sig = candidateLine.trim();
|
|
128
|
+
}
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
if (sig) {
|
|
@@ -125,8 +141,8 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
125
141
|
}
|
|
126
142
|
pendingComment = null;
|
|
127
143
|
}
|
|
128
|
-
// normalize: trim trailing { : etc, keep signature short
|
|
129
|
-
sig = sig.replace(/\s*\{\s*$/, '').replace(/:\s*$/, '').trim();
|
|
144
|
+
// normalize: trim trailing { : ; etc, keep signature short
|
|
145
|
+
sig = sig.replace(/\s*\{\s*$/, '').replace(/:\s*$/, '').replace(/;\s*$/, '').trim();
|
|
130
146
|
if (sig.length > 180) sig = sig.slice(0, 177) + '...';
|
|
131
147
|
const key = sig;
|
|
132
148
|
if (!seen.has(key)) {
|
|
@@ -147,10 +163,10 @@ export function skeletonize(text, { maxDepth = 3, language, includeImports = tru
|
|
|
147
163
|
const total = lines.filter(l => l.trim()).length;
|
|
148
164
|
const removed = total - out.length;
|
|
149
165
|
if (removed > 0 && out.length > 0) {
|
|
150
|
-
const commentPrefix = (language === 'py' || language === 'python') ? '# ' : '// ';
|
|
166
|
+
const commentPrefix = (language === 'py' || language === 'python') ? '# ' : (language === 'sql' ? '-- ' : '// ');
|
|
151
167
|
out.push(`${commentPrefix}... ${removed} lines collapsed (skeleton, maxDepth=${maxDepth})`);
|
|
152
168
|
}
|
|
153
169
|
return out.join('\n');
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
export default { skeletonize };
|
|
172
|
+
export default { skeletonize };
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
const React = require('react');
|
|
6
6
|
const NS = '@goodandready/dsh-context-lens';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const en = {
|
|
9
9
|
title: 'Context Lens & Token Guard',
|
|
10
10
|
sub: 'AST compression, log filtering, token budget',
|
|
11
11
|
mode: 'Compression mode',
|
|
@@ -23,39 +23,48 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
lowBudget: 'Budget nearly exhausted',
|
|
24
24
|
threshold: 'Auto-compress threshold (chars)',
|
|
25
25
|
autoCollapse: 'Warn when budget is nearly exhausted',
|
|
26
|
+
budgetAlertPercent: 'Budget alert threshold (%)',
|
|
26
27
|
previewApprox: 'Server preview',
|
|
27
28
|
noData: 'No data yet',
|
|
28
29
|
refresh: 'Refresh',
|
|
29
30
|
ops: 'ops',
|
|
30
31
|
active: 'Active',
|
|
31
32
|
readyState: 'Ready',
|
|
32
|
-
lowBudgetBadge: 'Low Budget ⚠'
|
|
33
|
+
lowBudgetBadge: 'Low Budget ⚠',
|
|
34
|
+
focusPaths: 'Active focus',
|
|
35
|
+
noFocus: 'No focus paths set',
|
|
36
|
+
clearFocus: 'Clear focus'
|
|
33
37
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
|
|
39
|
+
const zh = {
|
|
40
|
+
title: 'Context Lens 上下文镜头与 Token 守卫',
|
|
41
|
+
sub: 'AST 代码精简、日志过滤与 Token 预算监控',
|
|
42
|
+
mode: '压缩模式',
|
|
43
|
+
depth: 'AST 最大深度',
|
|
44
|
+
tracking: '追踪 Token 节省',
|
|
45
|
+
preview: '日志预览',
|
|
46
|
+
compress: '压缩',
|
|
47
|
+
saved: '已节省',
|
|
48
|
+
tokens: 'Token',
|
|
49
|
+
placeholder: '在此粘贴 Jest/Pytest 等测试日志…',
|
|
50
|
+
saving: '保存中…',
|
|
51
|
+
ready: '已就绪',
|
|
52
|
+
budget: '预算',
|
|
53
|
+
history: '近期操作',
|
|
54
|
+
lowBudget: 'Token 预算即将耗尽',
|
|
55
|
+
threshold: '自动压缩阈值 (字符数)',
|
|
56
|
+
autoCollapse: '预算偏低时在界面提示警告',
|
|
57
|
+
budgetAlertPercent: '预算警告阈值 (%)',
|
|
58
|
+
previewApprox: '服务端压缩预览',
|
|
59
|
+
noData: '暂无数据',
|
|
60
|
+
refresh: '刷新',
|
|
61
|
+
ops: '次操作',
|
|
62
|
+
active: '运行中',
|
|
63
|
+
readyState: '已就绪',
|
|
64
|
+
lowBudgetBadge: '预算紧张 ⚠',
|
|
65
|
+
focusPaths: '当前聚焦路径',
|
|
66
|
+
noFocus: '未设置聚焦路径',
|
|
67
|
+
clearFocus: '清除聚焦'
|
|
59
68
|
};
|
|
60
69
|
|
|
61
70
|
function ensureCss() {
|
|
@@ -220,6 +229,18 @@ window.__ModuleLoader__.load({
|
|
|
220
229
|
.cl-hist-item:last-child {
|
|
221
230
|
border-bottom: none;
|
|
222
231
|
}
|
|
232
|
+
.cl-tag {
|
|
233
|
+
display: inline-flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 4px;
|
|
236
|
+
padding: 2px 6px;
|
|
237
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
238
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
239
|
+
border-radius: 4px;
|
|
240
|
+
font-size: 11px;
|
|
241
|
+
font-family: monospace;
|
|
242
|
+
color: var(--dsw-alias-label-secondary);
|
|
243
|
+
}
|
|
223
244
|
.cl-error-box {
|
|
224
245
|
padding: 12px;
|
|
225
246
|
border-radius: 8px;
|
|
@@ -270,13 +291,13 @@ window.__ModuleLoader__.load({
|
|
|
270
291
|
try {
|
|
271
292
|
if (ctx && ctx.locale && typeof ctx.locale.getSnapshot === 'function') {
|
|
272
293
|
const snap = ctx.locale.getSnapshot();
|
|
273
|
-
if (snap && snap.active && snap.active.startsWith('
|
|
294
|
+
if (snap && snap.active && snap.active.startsWith('zh')) return zh;
|
|
274
295
|
}
|
|
275
296
|
} catch {}
|
|
276
297
|
return en;
|
|
277
298
|
}
|
|
278
299
|
|
|
279
|
-
function StatusPanel({ stats, history, labels, onRefresh, title, showBadge, compact }) {
|
|
300
|
+
function StatusPanel({ stats, history, focus, sessionId, labels, onRefresh, onClearFocus, title, showBadge, compact }) {
|
|
280
301
|
ensureCss();
|
|
281
302
|
const L = labels || en;
|
|
282
303
|
const warn = !!(stats && stats.lowBudget);
|
|
@@ -351,6 +372,33 @@ window.__ModuleLoader__.load({
|
|
|
351
372
|
));
|
|
352
373
|
}
|
|
353
374
|
|
|
375
|
+
// Active focus paths section
|
|
376
|
+
const focusPaths = (focus && focus.paths) || [];
|
|
377
|
+
if (focusPaths.length > 0 || onClearFocus) {
|
|
378
|
+
children.push(React.createElement('div', {
|
|
379
|
+
key: 'focus',
|
|
380
|
+
style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
381
|
+
},
|
|
382
|
+
React.createElement('div', {
|
|
383
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }
|
|
384
|
+
},
|
|
385
|
+
React.createElement('span', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-secondary)' } }, L.focusPaths),
|
|
386
|
+
focusPaths.length > 0 && onClearFocus
|
|
387
|
+
? React.createElement('button', {
|
|
388
|
+
type: 'button',
|
|
389
|
+
onClick: onClearFocus,
|
|
390
|
+
style: { background: 'none', border: 'none', padding: 0, fontSize: 10, color: 'var(--dsw-alias-label-tertiary)', cursor: 'pointer', textDecoration: 'underline' }
|
|
391
|
+
}, L.clearFocus)
|
|
392
|
+
: null
|
|
393
|
+
),
|
|
394
|
+
focusPaths.length > 0
|
|
395
|
+
? React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 } },
|
|
396
|
+
...focusPaths.slice(0, 4).map((p, i) => React.createElement('span', { key: i, className: 'cl-tag' }, p))
|
|
397
|
+
)
|
|
398
|
+
: React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', fontStyle: 'italic' } }, L.noFocus)
|
|
399
|
+
));
|
|
400
|
+
}
|
|
401
|
+
|
|
354
402
|
if (history && history.length) {
|
|
355
403
|
children.push(React.createElement('div', {
|
|
356
404
|
key: 'hist',
|
|
@@ -386,20 +434,31 @@ window.__ModuleLoader__.load({
|
|
|
386
434
|
.then((r) => r.ok ? r.json() : null);
|
|
387
435
|
}
|
|
388
436
|
|
|
437
|
+
function clearLensFocus(sessionId) {
|
|
438
|
+
const q = sessionId ? '?sessionId=' + encodeURIComponent(sessionId) : '';
|
|
439
|
+
return fetch('/dsh-context-lens/clear-focus' + q, { method: 'POST', headers: { accept: 'application/json' } })
|
|
440
|
+
.then((r) => r.ok ? r.json() : null);
|
|
441
|
+
}
|
|
442
|
+
|
|
389
443
|
function LensTabInner({ ctx: _ctx, scope }) {
|
|
390
444
|
ensureCss();
|
|
391
445
|
const [stats, setStats] = React.useState(null);
|
|
392
446
|
const [history, setHistory] = React.useState([]);
|
|
447
|
+
const [focus, setFocus] = React.useState(null);
|
|
448
|
+
const [sessionId, setSessionId] = React.useState(null);
|
|
393
449
|
const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
|
|
394
450
|
const apply = (j) => {
|
|
395
451
|
if (j && j.stats) setStats(j.stats);
|
|
396
452
|
if (j && j.history) setHistory(j.history || []);
|
|
453
|
+
if (j && j.focus) setFocus(j.focus);
|
|
454
|
+
if (j && j.sessionId) setSessionId(j.sessionId);
|
|
397
455
|
};
|
|
398
456
|
const refresh = () => fetchLensStatus().then(apply).catch(() => {});
|
|
457
|
+
const handleClearFocus = () => clearLensFocus(sessionId).then(() => refresh()).catch(() => {});
|
|
399
458
|
React.useEffect(() => { refresh(); }, []);
|
|
400
459
|
return React.createElement('div', { style: { padding: 12 } },
|
|
401
460
|
React.createElement(StatusPanel, {
|
|
402
|
-
stats, history, labels: L, onRefresh: refresh, title: 'Context Lens', showBadge: false, compact: false
|
|
461
|
+
stats, history, focus, sessionId, labels: L, onRefresh: refresh, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: false, compact: false
|
|
403
462
|
})
|
|
404
463
|
);
|
|
405
464
|
}
|
|
@@ -414,6 +473,8 @@ window.__ModuleLoader__.load({
|
|
|
414
473
|
ensureCss();
|
|
415
474
|
const [stats, setStats] = React.useState(null);
|
|
416
475
|
const [history, setHistory] = React.useState([]);
|
|
476
|
+
const [focus, setFocus] = React.useState(null);
|
|
477
|
+
const [sessionId, setSessionId] = React.useState(null);
|
|
417
478
|
const [open, setOpen] = React.useState(false);
|
|
418
479
|
const ref = React.useRef(null);
|
|
419
480
|
const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
|
|
@@ -421,8 +482,12 @@ window.__ModuleLoader__.load({
|
|
|
421
482
|
const fetchStatus = () => fetchLensStatus().then((j) => {
|
|
422
483
|
if (j && j.stats) setStats(j.stats);
|
|
423
484
|
if (j && j.history) setHistory(j.history || []);
|
|
485
|
+
if (j && j.focus) setFocus(j.focus);
|
|
486
|
+
if (j && j.sessionId) setSessionId(j.sessionId);
|
|
424
487
|
}).catch(() => {});
|
|
425
488
|
|
|
489
|
+
const handleClearFocus = () => clearLensFocus(sessionId).then(() => fetchStatus()).catch(() => {});
|
|
490
|
+
|
|
426
491
|
React.useEffect(() => {
|
|
427
492
|
let alive = true;
|
|
428
493
|
fetchStatus();
|
|
@@ -475,7 +540,7 @@ window.__ModuleLoader__.load({
|
|
|
475
540
|
onClick: (e) => e.stopPropagation()
|
|
476
541
|
},
|
|
477
542
|
React.createElement(StatusPanel, {
|
|
478
|
-
stats, history, labels: L, onRefresh: fetchStatus, title: 'Context Lens', showBadge: true, compact: true
|
|
543
|
+
stats, history, focus, sessionId, labels: L, onRefresh: fetchStatus, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: true, compact: true
|
|
479
544
|
})
|
|
480
545
|
) : null;
|
|
481
546
|
|
|
@@ -507,6 +572,7 @@ window.__ModuleLoader__.load({
|
|
|
507
572
|
astSkeletonMaxDepth: 3,
|
|
508
573
|
tokenSavingsTracking: true,
|
|
509
574
|
budgetLimit: 100000,
|
|
575
|
+
budgetAlertPercent: 90,
|
|
510
576
|
autoCollapse: true,
|
|
511
577
|
autoCompressThreshold: 4000
|
|
512
578
|
});
|
|
@@ -696,6 +762,14 @@ window.__ModuleLoader__.load({
|
|
|
696
762
|
onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
|
|
697
763
|
})
|
|
698
764
|
),
|
|
765
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
766
|
+
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budgetAlertPercent')),
|
|
767
|
+
React.createElement('input', {
|
|
768
|
+
className: 'cl-input', type: 'number', min: 50, max: 99, step: 1,
|
|
769
|
+
value: draft.budgetAlertPercent,
|
|
770
|
+
onChange: (e) => setDraft((d) => ({ ...d, budgetAlertPercent: parseInt(e.target.value, 10) || 90 }))
|
|
771
|
+
})
|
|
772
|
+
),
|
|
699
773
|
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
700
774
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('threshold')),
|
|
701
775
|
React.createElement('input', {
|
|
@@ -743,7 +817,7 @@ window.__ModuleLoader__.load({
|
|
|
743
817
|
|
|
744
818
|
module.exports.inject = ['slots', 'locale'];
|
|
745
819
|
module.exports.apply = function apply(ctx) {
|
|
746
|
-
try { ctx.locale.register(NS, { en,
|
|
820
|
+
try { ctx.locale.register(NS, { en, zh }); } catch (e) { console.warn('[dsh-context-lens] locale register failed', e && e.message || e); }
|
|
747
821
|
if (!ctx.slots) return;
|
|
748
822
|
let registered = false;
|
|
749
823
|
const doRegister = () => {
|
package/lib/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export const Config = z.object({
|
|
|
13
13
|
tokenSavingsTracking: z.boolean().default(true).description('Track and display token budget savings'),
|
|
14
14
|
autoCompressThreshold: z.number().default(4000).description('Auto-compress threshold in chars (0 to disable)'),
|
|
15
15
|
budgetLimit: z.number().default(100000).description('Session token budget; compressions stop counting after this limit'),
|
|
16
|
+
budgetAlertPercent: z.number().default(90).description('Budget percentage threshold (50-99%) for warning alert'),
|
|
16
17
|
autoCollapse: z.boolean().default(true).description('Warn in UI when budget is nearly exhausted (does not force-close the settings card)')
|
|
17
18
|
});
|
|
18
19
|
|
|
@@ -37,6 +38,14 @@ function getFocus(key) {
|
|
|
37
38
|
return focusBySession.get(key);
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
function clearFocus(key) {
|
|
42
|
+
if (key) {
|
|
43
|
+
focusBySession.set(key, { paths: [], updatedAt: new Date().toISOString() });
|
|
44
|
+
} else {
|
|
45
|
+
focusBySession.clear();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const OUTPUT_SCHEMA = { type: 'object', properties: { success: { type: 'boolean' } }, additionalProperties: true };
|
|
41
50
|
const renderOutput = (_args, result) => JSON.stringify(result, null, 2);
|
|
42
51
|
|
|
@@ -53,6 +62,7 @@ export function apply(ctx, config) {
|
|
|
53
62
|
const cfg = getConfig();
|
|
54
63
|
if (cfg && cfg.tokenSavingsTracking === false) return null;
|
|
55
64
|
if (cfg && typeof cfg.budgetLimit === 'number') tracker.setBudgetLimit(cfg.budgetLimit);
|
|
65
|
+
if (cfg && typeof cfg.budgetAlertPercent === 'number') tracker.setBudgetAlertPercent(cfg.budgetAlertPercent);
|
|
56
66
|
return tracker.record(original, compressed);
|
|
57
67
|
} catch {
|
|
58
68
|
// #39: never record on config/read errors
|
|
@@ -114,12 +124,12 @@ export function apply(ctx, config) {
|
|
|
114
124
|
|
|
115
125
|
ctx.tools.register({
|
|
116
126
|
name: 'context_lens_compress_code',
|
|
117
|
-
description: 'Generate AST skeleton for a large source file (JS/TS/Python/Go/Rust/Java) to save context',
|
|
127
|
+
description: 'Generate AST skeleton for a large source file (JS/TS/Python/Go/Rust/Java/C/C++/SQL) to save context',
|
|
118
128
|
parameters: {
|
|
119
129
|
type: 'object',
|
|
120
130
|
properties: {
|
|
121
131
|
code: { type: 'string', description: 'Source code to skeletonize' },
|
|
122
|
-
language: { type: 'string', enum: ['js', 'ts', 'py', 'python', 'go', 'rust', 'java'], description: 'Language hint' },
|
|
132
|
+
language: { type: 'string', enum: ['js', 'ts', 'py', 'python', 'go', 'rust', 'java', 'c', 'cpp', 'sql'], description: 'Language hint' },
|
|
123
133
|
maxDepth: { type: 'number', description: 'Max depth' },
|
|
124
134
|
filePath: { type: 'string', description: 'File path to check focus (if in focus, returns full code)' },
|
|
125
135
|
sessionId: { type: 'string', description: 'Session id for focus lookup' }
|
|
@@ -178,7 +188,8 @@ export function apply(ctx, config) {
|
|
|
178
188
|
execute: async (params, meta) => {
|
|
179
189
|
const cfg = getConfig();
|
|
180
190
|
if (typeof cfg.budgetLimit === 'number') tracker.setBudgetLimit(cfg.budgetLimit);
|
|
181
|
-
|
|
191
|
+
if (typeof cfg.budgetAlertPercent === 'number') tracker.setBudgetAlertPercent(cfg.budgetAlertPercent);
|
|
192
|
+
const stats = tracker.getStats(cfg.budgetLimit, cfg.budgetAlertPercent);
|
|
182
193
|
const history = tracker.getHistory();
|
|
183
194
|
const key = sessionKey(params || {}, meta);
|
|
184
195
|
return {
|
|
@@ -189,7 +200,8 @@ export function apply(ctx, config) {
|
|
|
189
200
|
autoCollapse: cfg.autoCollapse !== false,
|
|
190
201
|
focus: getFocus(key),
|
|
191
202
|
sessionId: key,
|
|
192
|
-
autoCompressThreshold: cfg.autoCompressThreshold
|
|
203
|
+
autoCompressThreshold: cfg.autoCompressThreshold,
|
|
204
|
+
budgetAlertPercent: cfg.budgetAlertPercent ?? 90
|
|
193
205
|
};
|
|
194
206
|
}
|
|
195
207
|
});
|
|
@@ -202,6 +214,7 @@ export function apply(ctx, config) {
|
|
|
202
214
|
handler: (req, res) => {
|
|
203
215
|
const cfg = getConfig();
|
|
204
216
|
if (typeof cfg.budgetLimit === 'number') tracker.setBudgetLimit(cfg.budgetLimit);
|
|
217
|
+
if (typeof cfg.budgetAlertPercent === 'number') tracker.setBudgetAlertPercent(cfg.budgetAlertPercent);
|
|
205
218
|
let sId = '__default__';
|
|
206
219
|
try {
|
|
207
220
|
const u = new URL(req.url, 'http://127.0.0.1');
|
|
@@ -211,7 +224,7 @@ export function apply(ctx, config) {
|
|
|
211
224
|
res.end(JSON.stringify({
|
|
212
225
|
ok: true,
|
|
213
226
|
plugin: 'dsh-context-lens',
|
|
214
|
-
stats: tracker.getStats(cfg.budgetLimit),
|
|
227
|
+
stats: tracker.getStats(cfg.budgetLimit, cfg.budgetAlertPercent),
|
|
215
228
|
history: tracker.getHistory(),
|
|
216
229
|
focus: getFocus(sId),
|
|
217
230
|
sessionId: sId
|
|
@@ -219,6 +232,23 @@ export function apply(ctx, config) {
|
|
|
219
232
|
}
|
|
220
233
|
}), 'dsh-context-lens status route');
|
|
221
234
|
|
|
235
|
+
// Quick clear-focus API route
|
|
236
|
+
ctx.effect(() => ctx.webServer.register({
|
|
237
|
+
kind: 'exact',
|
|
238
|
+
path: '/dsh-context-lens/clear-focus',
|
|
239
|
+
handler: async (req, res) => {
|
|
240
|
+
let sId = lastActiveSession || '__default__';
|
|
241
|
+
try {
|
|
242
|
+
const u = new URL(req.url, 'http://127.0.0.1');
|
|
243
|
+
const qId = u.searchParams.get('sessionId') || u.searchParams.get('session_id');
|
|
244
|
+
if (qId) sId = qId;
|
|
245
|
+
} catch {}
|
|
246
|
+
clearFocus(sId);
|
|
247
|
+
res.setHeader('content-type', 'application/json');
|
|
248
|
+
res.end(JSON.stringify({ ok: true, sessionId: sId, focus: getFocus(sId) }));
|
|
249
|
+
}
|
|
250
|
+
}), 'dsh-context-lens clear-focus route');
|
|
251
|
+
|
|
222
252
|
// #45: server-backed preview (same compressor as tools)
|
|
223
253
|
ctx.effect(() => ctx.webServer.register({
|
|
224
254
|
kind: 'exact',
|
|
@@ -242,6 +272,4 @@ export function apply(ctx, config) {
|
|
|
242
272
|
}
|
|
243
273
|
}), 'dsh-context-lens compress-preview route');
|
|
244
274
|
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export { compressLog, skeletonize, shouldAutoCompress };
|
|
275
|
+
}
|
package/lib/tokens/tracker.js
CHANGED
|
@@ -7,6 +7,7 @@ let calls = 0;
|
|
|
7
7
|
const history = []; // last 10
|
|
8
8
|
const MAX_HISTORY = 10;
|
|
9
9
|
let defaultBudgetLimit = 100000; // tokens
|
|
10
|
+
let defaultBudgetAlertPercent = 90; // threshold percent for low budget warning
|
|
10
11
|
|
|
11
12
|
export { estimateTokens };
|
|
12
13
|
|
|
@@ -16,6 +17,12 @@ export function setBudgetLimit(limit) {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export function setBudgetAlertPercent(percent) {
|
|
21
|
+
if (typeof percent === 'number' && percent >= 50 && percent <= 99) {
|
|
22
|
+
defaultBudgetAlertPercent = percent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
export function record(originalText, compressedText) {
|
|
20
27
|
const o = estimateTokens(originalText);
|
|
21
28
|
const c = estimateTokens(compressedText);
|
|
@@ -41,16 +48,19 @@ export function record(originalText, compressedText) {
|
|
|
41
48
|
return { originalTokens: o, compressedTokens: c, savedTokens: saved };
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
export function getStats(budgetLimitOverride) {
|
|
51
|
+
export function getStats(budgetLimitOverride, budgetAlertPercentOverride) {
|
|
45
52
|
const limit = (typeof budgetLimitOverride === 'number' && budgetLimitOverride > 0)
|
|
46
53
|
? budgetLimitOverride
|
|
47
54
|
: defaultBudgetLimit;
|
|
55
|
+
const alertPercent = (typeof budgetAlertPercentOverride === 'number' && budgetAlertPercentOverride >= 50 && budgetAlertPercentOverride <= 99)
|
|
56
|
+
? budgetAlertPercentOverride
|
|
57
|
+
: defaultBudgetAlertPercent;
|
|
48
58
|
const saved = Math.max(0, totalOriginal - totalCompressed);
|
|
49
59
|
const pct = totalOriginal ? Math.round((1 - totalCompressed / totalOriginal) * 100) : 0;
|
|
50
60
|
const budgetUsed = totalCompressed;
|
|
51
61
|
const budgetRemaining = Math.max(0, limit - budgetUsed);
|
|
52
62
|
const budgetPercent = Math.min(100, Math.round((budgetUsed / limit) * 100));
|
|
53
|
-
const lowBudget = budgetPercent
|
|
63
|
+
const lowBudget = budgetPercent >= alertPercent;
|
|
54
64
|
return {
|
|
55
65
|
totalOriginal,
|
|
56
66
|
totalCompressed,
|
|
@@ -62,6 +72,7 @@ export function getStats(budgetLimitOverride) {
|
|
|
62
72
|
budgetPercent,
|
|
63
73
|
lowBudget,
|
|
64
74
|
budgetLimit: limit,
|
|
75
|
+
budgetAlertPercent: alertPercent,
|
|
65
76
|
countingStopped: budgetUsed >= limit
|
|
66
77
|
};
|
|
67
78
|
}
|
|
@@ -79,7 +90,4 @@ export function reset() {
|
|
|
79
90
|
totalCompressed = 0;
|
|
80
91
|
calls = 0;
|
|
81
92
|
history.length = 0;
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export default { estimateTokens, setBudgetLimit, record, getStats, getHistory, clearHistory, reset };
|
|
93
|
+
}
|
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.17",
|
|
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",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"lib/",
|
|
15
15
|
"cordis.patch.yml",
|
|
16
16
|
"README.md",
|
|
17
|
+
"README.zh.md",
|
|
18
|
+
"README.ru.md",
|
|
17
19
|
"LICENSE"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|