claude-token-saver 3.8.0 → 3.8.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.en.md +8 -0
- package/README.md +8 -0
- package/package.json +1 -1
- package/src/brief.js +59 -7
package/README.en.md
CHANGED
|
@@ -263,6 +263,14 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
263
263
|
|
|
264
264
|
## Release notes
|
|
265
265
|
|
|
266
|
+
### v3.8.2 (2026-08-01)
|
|
267
|
+
- **Fixed: the context tier never came back down after compaction** — the tier was kept as a high-water mark, so a session that compacted at 80% stayed at tier 1 even though its context had just been emptied, and it refilled to the cap with no signal at all. The tier now follows the measurement down and warns again on the next climb.
|
|
268
|
+
- **Both windows are named** — with `autoCompactWindow` at 400k the briefing measured against 400k (80%) while Claude Code's own display measured against 1M (33%); two irreconcilable numbers for one session. The text now reads `자동 압축 창(400k)의 80% (… 화면의 1M 창 기준으로는 33%)`.
|
|
269
|
+
- **No "start a new session" advice when a compact window is set** — that threshold is exactly where compaction runs on its own, so the briefing suggests writing decisions and next steps to a file instead.
|
|
270
|
+
|
|
271
|
+
### v3.8.1 (2026-07-31)
|
|
272
|
+
- **Fixed: 1M sessions were judged against a 200k window** — the briefing inferred the window from the largest request seen so far, so a 1M session counted as 200k until it had already grown past 250k. At 160k of input it announced "past 80% of the 200k window" — really 16%. The window now comes from the configured model id, and when `autoCompactWindow` is set that is where the session actually turns over, so the percentage is measured against it (the text says `(autoCompactWindow 기준)`). The observed-size heuristic remains only as the fallback for an unreadable model id.
|
|
273
|
+
|
|
266
274
|
### v3.8.0 (2026-07-31)
|
|
267
275
|
- **New `compact-window` — 1M sessions had no compaction cap** — Claude Code compacts near `min(autoCompactWindow, model max context)`. On a 1M window with that value unset, compaction only fires around 800k, and every request until then re-bills the entire context. A 1M model with the value unset or above 400k now raises `🅷⚠ compact-window?` on the statusline plus a session briefing, and `compact-window set --global|--project` pins 400k. 200k sessions are exempt — the setting cannot change anything for them.
|
|
268
276
|
|
package/README.md
CHANGED
|
@@ -220,6 +220,14 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
220
220
|
|
|
221
221
|
## 릴리스 노트
|
|
222
222
|
|
|
223
|
+
### v3.8.2 (2026-08-01)
|
|
224
|
+
- **압축 뒤에도 경고 티어가 안 내려가던 문제 수정** — 컨텍스트 티어를 최고치로만 기억해서, 80%에서 자동 압축이 돌아 컨텍스트가 다시 비어도 티어가 1로 남았습니다. 그 세션은 창을 다시 꽉 채워도 아무 신호를 못 받았습니다. 이제 측정치가 내려가면 티어도 같이 내려가고, 다시 차오르면 정상적으로 경고합니다.
|
|
225
|
+
- **두 가지 창 표기 혼선 정리** — `autoCompactWindow`를 40만으로 잡으면 브리핑은 40만 기준(80%)인데 Claude Code 화면은 1M 기준(33%)이라, 같은 세션의 두 숫자가 서로 안 맞아 보였습니다. 이제 `자동 압축 창(400k)의 80%(… 화면의 1M 창 기준으로는 33%)`처럼 둘 다 적습니다.
|
|
226
|
+
- **압축 창이 설정돼 있으면 "새 세션 시작" 권고를 하지 않습니다** — 그 지점은 압축이 자동으로 처리하는 지점이라, 대신 결정·다음 할 일을 파일에 남기라고 안내합니다.
|
|
227
|
+
|
|
228
|
+
### v3.8.1 (2026-07-31)
|
|
229
|
+
- **1M 세션을 200k 창으로 오판하던 브리핑 버그 수정** — 세션 창을 "지금까지 본 가장 큰 요청"으로 추정해서, 1M 세션이라도 25만 토큰을 넘기 전까지는 200k로 취급했습니다. 그래서 입력 160k에서 "200k 창의 80%를 넘었습니다" 경고가 떴습니다(실제로는 16%). 이제 설정된 모델 ID로 창을 판정하고, `autoCompactWindow`가 잡혀 있으면 그 값이 실제로 세션이 넘어가는 지점이므로 그쪽을 기준으로 %를 계산합니다(문구에도 `(autoCompactWindow 기준)` 표기). 모델 ID를 못 읽는 경우에만 기존 관측치 추정으로 되돌아갑니다.
|
|
230
|
+
|
|
223
231
|
### v3.8.0 (2026-07-31)
|
|
224
232
|
- **`compact-window` 추가 — 1M 컨텍스트에서 자동 압축 지점이 방치되던 문제** — Claude Code는 `min(autoCompactWindow, 모델 최대 창)` 근처에서 압축합니다. 1M 창을 쓰면 이 값을 안 잡는 한 80만 토큰까지 커진 뒤에야 압축이 걸리고, 그전까지 모든 요청이 전체 컨텍스트를 재과금합니다. 이제 1M 모델인데 미설정이거나 40만 초과면 statusline `🅷⚠ compact-window?` + 세션 브리핑으로 알리고, `compact-window set --global|--project`로 40만을 고정합니다. 200k 컨텍스트는 설정이 영향을 주지 않으므로 경고 대상에서 제외합니다.
|
|
225
233
|
|
package/package.json
CHANGED
package/src/brief.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
29
29
|
import { join } from 'node:path';
|
|
30
30
|
import { userDataDir } from './paths.js';
|
|
31
|
+
import { resolveModelId, isOneMillionModel, effectiveWindow } from './compact-window.js';
|
|
31
32
|
|
|
32
33
|
// Context tiers as a fraction of the session's context window. Tier 1 warns
|
|
33
34
|
// (compaction/cost territory ahead), tier 2 urges wrapping up. A session only
|
|
@@ -36,7 +37,9 @@ export const CTX_TIERS = [
|
|
|
36
37
|
{ tier: 1, pct: 0.8 },
|
|
37
38
|
{ tier: 2, pct: 0.95 },
|
|
38
39
|
];
|
|
39
|
-
// Requests above this input size can only exist on a 1M window.
|
|
40
|
+
// Requests above this input size can only exist on a 1M window. Used as a
|
|
41
|
+
// fallback signal only — a 1M session that has not yet grown past 250k is
|
|
42
|
+
// still a 1M session, so the configured model decides first (see sessionCtx).
|
|
40
43
|
const WINDOW_1M_MIN_INPUT = 250_000;
|
|
41
44
|
const PRUNE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
42
45
|
const TAIL_BYTES = 256 * 1024;
|
|
@@ -67,11 +70,41 @@ function saveState(state, now) {
|
|
|
67
70
|
writeFileSync(briefStatePath(), JSON.stringify(state) + '\n');
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The window a tier crossing should be measured against.
|
|
75
|
+
*
|
|
76
|
+
* Two corrections over "infer from the biggest request seen":
|
|
77
|
+
* - The configured model decides the ceiling. A 1M session that has not yet
|
|
78
|
+
* grown past 250k is still a 1M session; judging it against 200k fired the
|
|
79
|
+
* 80% warning at 160k, less than a fifth of the real window.
|
|
80
|
+
* - `autoCompactWindow` lowers that ceiling. Once compaction is pinned at
|
|
81
|
+
* 400k, 400k — not 1M — is where the session actually turns over, so that
|
|
82
|
+
* is the number a "you are at 80%" warning has to mean.
|
|
83
|
+
*
|
|
84
|
+
* `observedMax` stays as a floor: it proves a 1M window even when the model id
|
|
85
|
+
* is unreadable (env override, settings we do not resolve).
|
|
86
|
+
*/
|
|
87
|
+
export function ctxWindowFor(observedMax = 0, root = process.cwd()) {
|
|
88
|
+
let modelWindow = observedMax > WINDOW_1M_MIN_INPUT ? 1_000_000 : 200_000;
|
|
89
|
+
let window = modelWindow;
|
|
90
|
+
let compactCapped = false;
|
|
91
|
+
try {
|
|
92
|
+
const { model } = resolveModelId(root);
|
|
93
|
+
if (isOneMillionModel(model)) modelWindow = window = 1_000_000;
|
|
94
|
+
const cap = effectiveWindow(root).value;
|
|
95
|
+
if (cap !== null && cap < window) {
|
|
96
|
+
window = cap;
|
|
97
|
+
compactCapped = true;
|
|
98
|
+
}
|
|
99
|
+
} catch { /* settings unreadable — the observed-size fallback still holds */ }
|
|
100
|
+
return { window, modelWindow, compactCapped };
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
/**
|
|
71
104
|
* Last request's input size for THIS session, from the transcript tail.
|
|
72
105
|
* Reads at most TAIL_BYTES — prompt-submit hooks must stay fast.
|
|
73
106
|
*/
|
|
74
|
-
export function sessionCtx(transcriptPath) {
|
|
107
|
+
export function sessionCtx(transcriptPath, { root = process.cwd() } = {}) {
|
|
75
108
|
let size;
|
|
76
109
|
try { size = statSync(transcriptPath).size; } catch { return null; }
|
|
77
110
|
const start = Math.max(0, size - TAIL_BYTES);
|
|
@@ -97,8 +130,8 @@ export function sessionCtx(transcriptPath) {
|
|
|
97
130
|
if (total > 0) { input = total; maxInput = Math.max(maxInput, total); }
|
|
98
131
|
}
|
|
99
132
|
if (input == null) return null;
|
|
100
|
-
const window
|
|
101
|
-
return { input, window, pct: input / window };
|
|
133
|
+
const { window, modelWindow, compactCapped } = ctxWindowFor(maxInput, root);
|
|
134
|
+
return { input, window, modelWindow, compactCapped, pct: input / window };
|
|
102
135
|
}
|
|
103
136
|
|
|
104
137
|
function ctxTierOf(pct) {
|
|
@@ -143,11 +176,30 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
|
|
|
143
176
|
const ctx = transcriptPath ? sessionCtx(transcriptPath) : null;
|
|
144
177
|
if (ctx) {
|
|
145
178
|
const tier = ctxTierOf(ctx.pct);
|
|
179
|
+
// The tier is not monotonic: auto-compaction drops the live context back to
|
|
180
|
+
// a fraction of the window, which starts a new fill cycle. Holding the old
|
|
181
|
+
// high-water tier meant a session that compacted at 80% was never warned
|
|
182
|
+
// again — it silently refilled to the cap with no signal at all.
|
|
183
|
+
if (tier < (s.ctxTier || 0)) s.ctxTier = tier;
|
|
146
184
|
if (tier > (s.ctxTier || 0)) {
|
|
147
|
-
|
|
185
|
+
// Name both denominators when they differ. Claude Code's own UI counts
|
|
186
|
+
// against the model window, so a bare "400k 창의 80%" reads as wrong to
|
|
187
|
+
// anyone looking at a statusline that says 32% of 1M — same session,
|
|
188
|
+
// two different windows, no way to reconcile them from the text alone.
|
|
189
|
+
const winLabel = ctx.window >= 1_000_000 ? '1M' : fmtK(ctx.window);
|
|
190
|
+
const modelPct = Math.round((ctx.input / ctx.modelWindow) * 100);
|
|
191
|
+
const modelLabel = ctx.modelWindow >= 1_000_000 ? '1M' : fmtK(ctx.modelWindow);
|
|
192
|
+
const capNote = ctx.compactCapped ? `, 화면의 ${modelLabel} 창 기준으로는 ${modelPct}%` : '';
|
|
193
|
+
// With autoCompactWindow set, crossing the threshold means compaction is
|
|
194
|
+
// about to run on its own. Telling the user to start a new session there
|
|
195
|
+
// would be advice for a problem the setting already handles.
|
|
148
196
|
items.push(tier === 2
|
|
149
|
-
?
|
|
150
|
-
|
|
197
|
+
? (ctx.compactCapped
|
|
198
|
+
? `이 세션의 컨텍스트가 자동 압축 창(${winLabel})의 95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}${capNote}). 곧 자동 압축이 돌아 이전 대화가 요약으로 바뀝니다 — 지금 단계를 마무리하고 이어서 할 일은 파일에 적어두면 압축 뒤에도 안전합니다.`
|
|
199
|
+
: `이 세션의 컨텍스트가 ${winLabel} 창의 95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 곧 자동 압축으로 맥락 손실이 생길 수 있으니, 진행 중인 작업을 일단락하고 새 세션을 시작하는 편이 좋습니다.`)
|
|
200
|
+
: (ctx.compactCapped
|
|
201
|
+
? `이 세션의 컨텍스트가 자동 압축 창(${winLabel})의 80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}${capNote}). 설정해 둔 압축 지점이 가까워졌습니다 — 압축은 알아서 돌아가니 새 세션을 서두를 필요는 없고, 여기까지의 결정과 다음 할 일만 파일에 남겨두면 됩니다.`
|
|
202
|
+
: `이 세션의 컨텍스트가 ${winLabel} 창의 80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 이후 요청은 비용이 커지는 구간입니다 — 작업이 일단락되면 새 세션 시작을 권합니다.`));
|
|
151
203
|
s.ctxTier = tier;
|
|
152
204
|
}
|
|
153
205
|
}
|