claude-token-saver 3.8.0 → 3.8.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/README.en.md +3 -0
- package/README.md +3 -0
- package/package.json +1 -1
- package/src/brief.js +43 -7
package/README.en.md
CHANGED
|
@@ -263,6 +263,9 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
263
263
|
|
|
264
264
|
## Release notes
|
|
265
265
|
|
|
266
|
+
### v3.8.1 (2026-07-31)
|
|
267
|
+
- **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.
|
|
268
|
+
|
|
266
269
|
### v3.8.0 (2026-07-31)
|
|
267
270
|
- **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
271
|
|
package/README.md
CHANGED
|
@@ -220,6 +220,9 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
220
220
|
|
|
221
221
|
## 릴리스 노트
|
|
222
222
|
|
|
223
|
+
### v3.8.1 (2026-07-31)
|
|
224
|
+
- **1M 세션을 200k 창으로 오판하던 브리핑 버그 수정** — 세션 창을 "지금까지 본 가장 큰 요청"으로 추정해서, 1M 세션이라도 25만 토큰을 넘기 전까지는 200k로 취급했습니다. 그래서 입력 160k에서 "200k 창의 80%를 넘었습니다" 경고가 떴습니다(실제로는 16%). 이제 설정된 모델 ID로 창을 판정하고, `autoCompactWindow`가 잡혀 있으면 그 값이 실제로 세션이 넘어가는 지점이므로 그쪽을 기준으로 %를 계산합니다(문구에도 `(autoCompactWindow 기준)` 표기). 모델 ID를 못 읽는 경우에만 기존 관측치 추정으로 되돌아갑니다.
|
|
225
|
+
|
|
223
226
|
### v3.8.0 (2026-07-31)
|
|
224
227
|
- **`compact-window` 추가 — 1M 컨텍스트에서 자동 압축 지점이 방치되던 문제** — Claude Code는 `min(autoCompactWindow, 모델 최대 창)` 근처에서 압축합니다. 1M 창을 쓰면 이 값을 안 잡는 한 80만 토큰까지 커진 뒤에야 압축이 걸리고, 그전까지 모든 요청이 전체 컨텍스트를 재과금합니다. 이제 1M 모델인데 미설정이거나 40만 초과면 statusline `🅷⚠ compact-window?` + 세션 브리핑으로 알리고, `compact-window set --global|--project`로 40만을 고정합니다. 200k 컨텍스트는 설정이 영향을 주지 않으므로 경고 대상에서 제외합니다.
|
|
225
228
|
|
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,40 @@ 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 window = observedMax > WINDOW_1M_MIN_INPUT ? 1_000_000 : 200_000;
|
|
89
|
+
let compactCapped = false;
|
|
90
|
+
try {
|
|
91
|
+
const { model } = resolveModelId(root);
|
|
92
|
+
if (isOneMillionModel(model)) window = 1_000_000;
|
|
93
|
+
const cap = effectiveWindow(root).value;
|
|
94
|
+
if (cap !== null && cap < window) {
|
|
95
|
+
window = cap;
|
|
96
|
+
compactCapped = true;
|
|
97
|
+
}
|
|
98
|
+
} catch { /* settings unreadable — the observed-size fallback still holds */ }
|
|
99
|
+
return { window, compactCapped };
|
|
100
|
+
}
|
|
101
|
+
|
|
70
102
|
/**
|
|
71
103
|
* Last request's input size for THIS session, from the transcript tail.
|
|
72
104
|
* Reads at most TAIL_BYTES — prompt-submit hooks must stay fast.
|
|
73
105
|
*/
|
|
74
|
-
export function sessionCtx(transcriptPath) {
|
|
106
|
+
export function sessionCtx(transcriptPath, { root = process.cwd() } = {}) {
|
|
75
107
|
let size;
|
|
76
108
|
try { size = statSync(transcriptPath).size; } catch { return null; }
|
|
77
109
|
const start = Math.max(0, size - TAIL_BYTES);
|
|
@@ -97,8 +129,8 @@ export function sessionCtx(transcriptPath) {
|
|
|
97
129
|
if (total > 0) { input = total; maxInput = Math.max(maxInput, total); }
|
|
98
130
|
}
|
|
99
131
|
if (input == null) return null;
|
|
100
|
-
const window = maxInput
|
|
101
|
-
return { input, window, pct: input / window };
|
|
132
|
+
const { window, compactCapped } = ctxWindowFor(maxInput, root);
|
|
133
|
+
return { input, window, compactCapped, pct: input / window };
|
|
102
134
|
}
|
|
103
135
|
|
|
104
136
|
function ctxTierOf(pct) {
|
|
@@ -144,10 +176,14 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
|
|
|
144
176
|
if (ctx) {
|
|
145
177
|
const tier = ctxTierOf(ctx.pct);
|
|
146
178
|
if (tier > (s.ctxTier || 0)) {
|
|
147
|
-
|
|
179
|
+
// Name the window the percentage was actually computed against — when
|
|
180
|
+
// autoCompactWindow caps a 1M model at 400k, "1M 창의 80%" would be a
|
|
181
|
+
// number the user cannot reconcile with anything they configured.
|
|
182
|
+
const winLabel = ctx.window >= 1_000_000 ? '1M' : fmtK(ctx.window);
|
|
183
|
+
const capNote = ctx.compactCapped ? ' (autoCompactWindow 기준)' : '';
|
|
148
184
|
items.push(tier === 2
|
|
149
|
-
? `이 세션의 컨텍스트가 ${winLabel}
|
|
150
|
-
: `이 세션의 컨텍스트가 ${winLabel}
|
|
185
|
+
? `이 세션의 컨텍스트가 ${winLabel} 창${capNote}의 95%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 곧 자동 압축으로 맥락 손실이 생길 수 있으니, 진행 중인 작업을 일단락하고 새 세션을 시작하는 편이 좋습니다.`
|
|
186
|
+
: `이 세션의 컨텍스트가 ${winLabel} 창${capNote}의 80%를 넘었습니다(직전 요청 입력 ${fmtK(ctx.input)}). 이후 요청은 비용이 커지는 구간입니다 — 작업이 일단락되면 새 세션 시작을 권합니다.`);
|
|
151
187
|
s.ctxTier = tier;
|
|
152
188
|
}
|
|
153
189
|
}
|