mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
Merge #777: auto-compact and auto-clear from the statusLine context size
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
commit
6fa2dfa549
16 files changed
+579
-45
No files matched your search
+131
-32
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* @fileoverview Auto-compact, auto-clear, and auto-resume automation for Session.
|
||||
*
|
||||
* Monitors token counts and triggers /compact or /clear commands when
|
||||
* configurable thresholds are reached. Waits for Claude to be idle
|
||||
* before sending commands, with retry logic and mutual exclusion
|
||||
* (compact and clear never run simultaneously).
|
||||
* Monitors the context size and triggers /compact or /clear commands when
|
||||
* configurable thresholds are reached. The size comes from the statusline report
|
||||
* when the session has one and from the session's token counter otherwise (see
|
||||
* decideAutoThreshold). Waits for Claude to be idle before sending commands, with
|
||||
* retry logic and mutual exclusion (compact and clear never run simultaneously).
|
||||
*
|
||||
* Also implements auto-resume on usage limit ("token pause" control):
|
||||
* when enabled and Claude stops on a usage-limit message ("5-hour limit
|
||||
@@ -114,8 +115,8 @@ const RESUME_PROMPT = 'continue';
|
||||
/** Minimum valid threshold for auto-clear/compact (1000 tokens) */
|
||||
const MIN_AUTO_THRESHOLD = 1000;
|
||||
|
||||
/** Maximum valid threshold for auto-clear/compact (500k tokens) */
|
||||
const MAX_AUTO_THRESHOLD = 500_000;
|
||||
/** Maximum valid threshold for auto-clear/compact when the model's window is not reported (1M tokens) */
|
||||
const MAX_AUTO_THRESHOLD = 1_000_000;
|
||||
|
||||
/** Default auto-clear threshold when invalid value provided */
|
||||
const DEFAULT_AUTO_CLEAR_THRESHOLD = 140_000;
|
||||
@@ -123,6 +124,61 @@ const DEFAULT_AUTO_CLEAR_THRESHOLD = 140_000;
|
||||
/** Default auto-compact threshold when invalid value provided */
|
||||
const DEFAULT_AUTO_COMPACT_THRESHOLD = 110_000;
|
||||
|
||||
/** The context size Claude Code last reported on its statusline. */
|
||||
export interface ContextReport {
|
||||
/** Tokens in the context window at the last API response, cache reads and writes included. */
|
||||
tokens: number;
|
||||
/** The model's window (200k, or 1M with extended context); absent when not reported. */
|
||||
windowSize?: number;
|
||||
}
|
||||
|
||||
export interface AutoThresholdInput {
|
||||
threshold: number;
|
||||
/** The newest statusline report, or null when none has arrived. */
|
||||
report: ContextReport | null;
|
||||
/** The newest report's size when the last /compact or /clear went out, or null. */
|
||||
reportTokensAtCommand: number | null;
|
||||
/** The session's token counter. */
|
||||
counterTokens: number;
|
||||
/** The counter's value at the last /compact: it never drops by itself after one. */
|
||||
counterBaseline: number;
|
||||
}
|
||||
|
||||
export interface AutoThresholdDecision {
|
||||
trigger: boolean;
|
||||
tokens: number;
|
||||
source: 'statusline' | 'counter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the context has reached a threshold. Pure, so every case is testable without a session.
|
||||
*
|
||||
* A report wins over the counter: the counter leaves out cache reads, so on a long conversation
|
||||
* it is a small fraction of the real context. The counter stays as the fallback, because the
|
||||
* exporter only runs for Claude, only while plan-usage collection is on, and never on a remote or
|
||||
* Docker launch or in a workspace with its own statusline.
|
||||
*
|
||||
* A report carries the usage of the last API response, so after a /compact or /clear the number
|
||||
* from before the command stays the newest one until the command finishes, and it is re-posted on
|
||||
* every render. It is ignored by VALUE rather than for a length of time: the first report that
|
||||
* differs came from the finished command (0 on Claude Code 2.1.319) or from a later API response.
|
||||
* If the command was swallowed that report is higher and the threshold fires again, so nothing
|
||||
* can lock auto-compact off, and no timer has to guess how long a compact takes (601k tokens took
|
||||
* 39 s, four times the cooldown).
|
||||
*
|
||||
* A counter summed from JSON usage never drops after a compact, so it counts from the value at
|
||||
* the last one. The pane parser's counter is absolute, see noteAbsoluteCounter().
|
||||
*/
|
||||
export function decideAutoThreshold(input: AutoThresholdInput): AutoThresholdDecision {
|
||||
const { threshold, report, reportTokensAtCommand, counterTokens, counterBaseline } = input;
|
||||
if (report) {
|
||||
const stale = report.tokens === reportTokensAtCommand;
|
||||
return { trigger: !stale && report.tokens >= threshold, tokens: report.tokens, source: 'statusline' };
|
||||
}
|
||||
const tokens = Math.max(0, counterTokens - counterBaseline);
|
||||
return { trigger: tokens >= threshold, tokens, source: 'counter' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks required by SessionAutoOps to interact with the parent Session.
|
||||
*/
|
||||
@@ -135,6 +191,8 @@ interface AutoOpsCallbacks {
|
||||
isStopped: () => boolean;
|
||||
/** Get current total token count (input + output) */
|
||||
getTotalTokens: () => number;
|
||||
/** The newest statusline context report, or null when none has arrived */
|
||||
getContextReport: () => ContextReport | null;
|
||||
/** Get session ID for logging */
|
||||
getSessionId: () => string;
|
||||
}
|
||||
@@ -156,6 +214,8 @@ export class SessionAutoOps extends EventEmitter {
|
||||
private _autoCompactEnabled: boolean = false;
|
||||
private _autoCompactPrompt: string = '';
|
||||
private _isCompacting: boolean = false;
|
||||
private _reportTokensAtCommand: number | null = null;
|
||||
private _counterBaseline: number = 0;
|
||||
private _autoCompactTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
// Auto-clear state
|
||||
@@ -206,14 +266,7 @@ export class SessionAutoOps extends EventEmitter {
|
||||
setAutoCompact(enabled: boolean, threshold?: number, prompt?: string): void {
|
||||
this._autoCompactEnabled = enabled;
|
||||
if (threshold !== undefined) {
|
||||
if (threshold < MIN_AUTO_THRESHOLD || threshold > MAX_AUTO_THRESHOLD) {
|
||||
console.warn(
|
||||
`[SessionAutoOps ${this.callbacks.getSessionId()}] Invalid autoCompact threshold ${threshold}, must be between ${MIN_AUTO_THRESHOLD} and ${MAX_AUTO_THRESHOLD}. Using default ${DEFAULT_AUTO_COMPACT_THRESHOLD}.`
|
||||
);
|
||||
this._autoCompactThreshold = DEFAULT_AUTO_COMPACT_THRESHOLD;
|
||||
} else {
|
||||
this._autoCompactThreshold = threshold;
|
||||
}
|
||||
this._autoCompactThreshold = this._resolveThreshold('autoCompact', threshold, DEFAULT_AUTO_COMPACT_THRESHOLD);
|
||||
}
|
||||
if (prompt !== undefined) {
|
||||
this._autoCompactPrompt = prompt;
|
||||
@@ -239,14 +292,7 @@ export class SessionAutoOps extends EventEmitter {
|
||||
setAutoClear(enabled: boolean, threshold?: number): void {
|
||||
this._autoClearEnabled = enabled;
|
||||
if (threshold !== undefined) {
|
||||
if (threshold < MIN_AUTO_THRESHOLD || threshold > MAX_AUTO_THRESHOLD) {
|
||||
console.warn(
|
||||
`[SessionAutoOps ${this.callbacks.getSessionId()}] Invalid autoClear threshold ${threshold}, must be between ${MIN_AUTO_THRESHOLD} and ${MAX_AUTO_THRESHOLD}. Using default ${DEFAULT_AUTO_CLEAR_THRESHOLD}.`
|
||||
);
|
||||
this._autoClearThreshold = DEFAULT_AUTO_CLEAR_THRESHOLD;
|
||||
} else {
|
||||
this._autoClearThreshold = threshold;
|
||||
}
|
||||
this._autoClearThreshold = this._resolveThreshold('autoClear', threshold, DEFAULT_AUTO_CLEAR_THRESHOLD);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,19 +439,65 @@ export class SessionAutoOps extends EventEmitter {
|
||||
// Threshold checks
|
||||
// ============================================================================
|
||||
|
||||
/** The highest threshold that can ever fire: the reported window, or 1M when it is unknown. */
|
||||
/**
|
||||
* Check if auto-compact should be triggered based on current token count.
|
||||
* Called after token count updates.
|
||||
* A value above the window stops at the window rather than falling to the default: a threshold
|
||||
* saved under a larger window comes back on every save of the options, and the default would
|
||||
* compact far earlier than anyone asked for.
|
||||
*/
|
||||
private _resolveThreshold(name: string, threshold: number, fallback: number): number {
|
||||
const id = this.callbacks.getSessionId();
|
||||
if (threshold < MIN_AUTO_THRESHOLD) {
|
||||
console.warn(
|
||||
`[SessionAutoOps ${id}] Invalid ${name} threshold ${threshold}, minimum is ${MIN_AUTO_THRESHOLD}. Using default ${fallback}.`
|
||||
);
|
||||
return fallback;
|
||||
}
|
||||
const max = this._maxThreshold();
|
||||
if (threshold > max) {
|
||||
console.warn(`[SessionAutoOps ${id}] ${name} threshold ${threshold} is above the window, using ${max}.`);
|
||||
return max;
|
||||
}
|
||||
return threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pane parser's counter holds the absolute number on screen and only moves past its highest
|
||||
* value so far, so a move means the context grew back past the last compact. Counting that from
|
||||
* the baseline would ask for twice the threshold, which a 200k window never reaches.
|
||||
*/
|
||||
noteAbsoluteCounter(): void {
|
||||
this._counterBaseline = 0;
|
||||
}
|
||||
|
||||
private _maxThreshold(): number {
|
||||
return this.callbacks.getContextReport()?.windowSize ?? MAX_AUTO_THRESHOLD;
|
||||
}
|
||||
|
||||
private _decide(threshold: number): AutoThresholdDecision {
|
||||
return decideAutoThreshold({
|
||||
threshold,
|
||||
report: this.callbacks.getContextReport(),
|
||||
reportTokensAtCommand: this._reportTokensAtCommand,
|
||||
counterTokens: this.callbacks.getTotalTokens(),
|
||||
counterBaseline: this._counterBaseline,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if auto-compact should be triggered based on the current context size.
|
||||
* Called after token count updates and after each statusline report.
|
||||
*/
|
||||
checkAutoCompact(): void {
|
||||
if (this.callbacks.isStopped()) return;
|
||||
if (!this._autoCompactEnabled || this._isCompacting || this._isClearing) return;
|
||||
|
||||
const totalTokens = this.callbacks.getTotalTokens();
|
||||
if (totalTokens >= this._autoCompactThreshold) {
|
||||
const decision = this._decide(this._autoCompactThreshold);
|
||||
if (decision.trigger) {
|
||||
const totalTokens = decision.tokens;
|
||||
this._isCompacting = true;
|
||||
console.log(
|
||||
`[SessionAutoOps] Auto-compact triggered: ${totalTokens} tokens >= ${this._autoCompactThreshold} threshold`
|
||||
`[SessionAutoOps] Auto-compact triggered: ${totalTokens} tokens (${decision.source}) >= ${this._autoCompactThreshold} threshold`
|
||||
);
|
||||
|
||||
const action = async () => {
|
||||
@@ -414,6 +506,9 @@ export class SessionAutoOps extends EventEmitter {
|
||||
const compactCmd = this._autoCompactPrompt
|
||||
? `/compact ${toSingleLine(this._autoCompactPrompt)}\r`
|
||||
: '/compact\r';
|
||||
// Read here, not at the trigger: the idle wait can outlast several reports.
|
||||
this._reportTokensAtCommand = this.callbacks.getContextReport()?.tokens ?? null;
|
||||
this._counterBaseline = this.callbacks.getTotalTokens();
|
||||
await this.callbacks.writeCommand(compactCmd);
|
||||
this.emit('autoCompact', {
|
||||
tokens: totalTokens,
|
||||
@@ -446,21 +541,25 @@ export class SessionAutoOps extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if auto-clear should be triggered based on current token count.
|
||||
* Called after token count updates.
|
||||
* Check if auto-clear should be triggered based on the current context size.
|
||||
* Called after token count updates and after each statusline report.
|
||||
*/
|
||||
checkAutoClear(): void {
|
||||
if (this.callbacks.isStopped()) return;
|
||||
if (!this._autoClearEnabled || this._isClearing || this._isCompacting) return;
|
||||
|
||||
const totalTokens = this.callbacks.getTotalTokens();
|
||||
if (totalTokens >= this._autoClearThreshold) {
|
||||
const decision = this._decide(this._autoClearThreshold);
|
||||
if (decision.trigger) {
|
||||
const totalTokens = decision.tokens;
|
||||
this._isClearing = true;
|
||||
console.log(
|
||||
`[SessionAutoOps] Auto-clear triggered: ${totalTokens} tokens >= ${this._autoClearThreshold} threshold`
|
||||
`[SessionAutoOps] Auto-clear triggered: ${totalTokens} tokens (${decision.source}) >= ${this._autoClearThreshold} threshold`
|
||||
);
|
||||
|
||||
const action = async () => {
|
||||
this._reportTokensAtCommand = this.callbacks.getContextReport()?.tokens ?? null;
|
||||
// The session zeroes its counter on 'autoClear', so the counter restarts from 0.
|
||||
this._counterBaseline = 0;
|
||||
await this.callbacks.writeCommand('/clear\r');
|
||||
this.emit('autoClear', { tokens: totalTokens, threshold: this._autoClearThreshold });
|
||||
};
|
||||
|
||||
+21
-6
@@ -153,7 +153,7 @@ import {
|
||||
buildMuxAttachEnv,
|
||||
buildShellEnv,
|
||||
} from './session-cli-builder.js';
|
||||
import { SessionAutoOps } from './session-auto-ops.js';
|
||||
import { SessionAutoOps, type ContextReport } from './session-auto-ops.js';
|
||||
import { detectUsageLimitPause } from './usage-limit-patterns.js';
|
||||
import { SessionTaskCache } from './session-task-cache.js';
|
||||
import { InteractivePtyExitBreaker } from './session-pty-exit-breaker.js';
|
||||
@@ -675,6 +675,7 @@ export class Session extends EventEmitter {
|
||||
private _watching: string | null = null;
|
||||
/** Last `prompt_cache` report from the statusline exporter; null until Claude sends one. */
|
||||
private _promptCache: PromptCacheStatus | null = null;
|
||||
private _contextReport: ContextReport | null = null;
|
||||
/** Prompt-cache keep-warm opt-in; undefined = follow the app-wide default. Persisted. */
|
||||
private _keepWarmEnabled: boolean | undefined;
|
||||
/** Last status the keep-warm service published for this session; ephemeral. */
|
||||
@@ -1290,6 +1291,7 @@ export class Session extends EventEmitter {
|
||||
isWorking: () => this._isWorking,
|
||||
isStopped: () => this._isStopped,
|
||||
getTotalTokens: () => this._totalInputTokens + this._totalOutputTokens,
|
||||
getContextReport: () => this._contextReport,
|
||||
getSessionId: () => this.id,
|
||||
});
|
||||
this._autoOps.on('autoCompact', (data) => this.emit('autoCompact', data));
|
||||
@@ -1485,6 +1487,7 @@ export class Session extends EventEmitter {
|
||||
// rather than show the previous conversation's "warm until" against the new one; the
|
||||
// next statusline render repopulates. A same-id reattach returned above, untouched.
|
||||
this._promptCache = null;
|
||||
this._contextReport = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1833,6 +1836,17 @@ export class Session extends EventEmitter {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the context size from a statusline report and run the threshold checks on it. The
|
||||
* token paths above cannot do this: they only run when the counter moves, and the counter
|
||||
* does not see the cached context.
|
||||
*/
|
||||
noteContextReport(report: ContextReport): void {
|
||||
this._contextReport = report;
|
||||
this._autoOps.checkAutoCompact();
|
||||
this._autoOps.checkAutoClear();
|
||||
}
|
||||
|
||||
/** Prompt-cache keep-warm opt-in; undefined means the app-wide default applies. */
|
||||
get keepWarmEnabled(): boolean | undefined {
|
||||
return this._keepWarmEnabled;
|
||||
@@ -2479,6 +2493,7 @@ export class Session extends EventEmitter {
|
||||
autoResumeEnabled: this._autoOps.autoResumeEnabled,
|
||||
autoResumeAt: this._autoOps.autoResumeAt ?? undefined,
|
||||
promptCache: this._promptCache ?? undefined,
|
||||
contextWindowSize: this._contextReport?.windowSize,
|
||||
statusLineExporter: this.statusLineExporter,
|
||||
keepWarmEnabled: this._keepWarmEnabled,
|
||||
keepWarm: this._keepWarm ?? undefined,
|
||||
@@ -5006,9 +5021,9 @@ export class Session extends EventEmitter {
|
||||
// Matches patterns like "123.4k tokens", "5234 tokens", "1.2M tokens"
|
||||
//
|
||||
// SAFETY LIMITS:
|
||||
// - Max tokens per session: 500k (Claude's context is ~200k)
|
||||
// - Max tokens per session: MAX_SESSION_TOKENS (the largest context Claude reports is 1M)
|
||||
// - Max delta per update: 100k (prevents sudden jumps from parsing errors)
|
||||
// - Rejects "M" suffix values > 0.5 (500k) to prevent false matches
|
||||
// - Rejects "M" suffix values above that maximum to prevent false matches
|
||||
private parseTokensFromStatusLine(cleanData: string): void {
|
||||
// Quick pre-check: skip expensive regex if "token" not present (performance optimization)
|
||||
if (!cleanData.includes('token')) return;
|
||||
@@ -5026,9 +5041,8 @@ export class Session extends EventEmitter {
|
||||
if (suffix === 'k') {
|
||||
tokenCount *= 1000;
|
||||
} else if (suffix === 'm') {
|
||||
// Safety: Reject M values that would result in > 500k tokens
|
||||
// Claude's context window is ~200k, so anything claiming millions is likely a false match
|
||||
if (tokenCount > 0.5) {
|
||||
// Safety: anything claiming more than the largest context is likely a false match
|
||||
if (tokenCount * 1000000 > MAX_SESSION_TOKENS) {
|
||||
console.warn(
|
||||
`[Session ${this.id}] Rejected suspicious M token value: ${tokenMatch[0]} (would be ${tokenCount * 1000000} tokens)`
|
||||
);
|
||||
@@ -5064,6 +5078,7 @@ export class Session extends EventEmitter {
|
||||
this._totalOutputTokens += Math.round(delta * 0.4);
|
||||
|
||||
// Check if we should auto-compact or auto-clear
|
||||
this._autoOps.noteAbsoluteCounter();
|
||||
this._autoOps.checkAutoCompact();
|
||||
this._autoOps.checkAutoClear();
|
||||
}
|
||||
|
||||
@@ -1017,6 +1017,8 @@ export interface SessionState {
|
||||
* `expiresAt` has passed reads as cold rather than as a stale warm.
|
||||
*/
|
||||
promptCache?: PromptCacheStatus;
|
||||
/** The model's context window in tokens from the last statusline report; absent until one arrives. */
|
||||
contextWindowSize?: number;
|
||||
/** Why a cache report will or will not arrive (see `StatusLineExporter`); absent when unknown. */
|
||||
statusLineExporter?: StatusLineExporter;
|
||||
/** Prompt-cache keep-warm opt-in; absent = follow the app-wide `promptCacheKeepWarm` default. */
|
||||
|
||||
+12
-1
@@ -68,7 +68,12 @@ export interface RawStatuslinePayload {
|
||||
five_hour?: { used_percentage?: number; resets_at?: number };
|
||||
seven_day?: { used_percentage?: number; resets_at?: number };
|
||||
};
|
||||
context_window?: { used_percentage?: number; total_input_tokens?: number; total_output_tokens?: number };
|
||||
context_window?: {
|
||||
used_percentage?: number;
|
||||
total_input_tokens?: number;
|
||||
total_output_tokens?: number;
|
||||
context_window_size?: number;
|
||||
};
|
||||
cost?: { total_cost_usd?: number };
|
||||
model?: { display_name?: string };
|
||||
/** Main-conversation prompt-cache statistics (Claude Code v2.1.251+); absent before the first API response. */
|
||||
@@ -259,6 +264,8 @@ export interface SessionStatus {
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
contextUsedPercentage?: number;
|
||||
/** The model's context window in tokens (200k, or 1M with extended context). */
|
||||
contextWindowSize?: number;
|
||||
/** Prompt-cache state of the main conversation, when the statusline reports it. */
|
||||
cache?: PromptCacheStatus;
|
||||
}
|
||||
@@ -287,6 +294,10 @@ export function parseSessionStatus(data: RawStatuslinePayload | undefined): Sess
|
||||
if (typeof cw?.used_percentage === 'number') {
|
||||
s.contextUsedPercentage = clampPct(cw.used_percentage);
|
||||
}
|
||||
const windowSize = cw?.context_window_size;
|
||||
if (typeof windowSize === 'number' && Number.isFinite(windowSize) && windowSize > 0) {
|
||||
s.contextWindowSize = windowSize;
|
||||
}
|
||||
const cache = parsePromptCache(data);
|
||||
if (cache) s.cache = cache;
|
||||
return Object.keys(s).length ? s : null;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* @fileoverview Token validation constants.
|
||||
*
|
||||
* Claude's context window is ~200k tokens, so 500k is a generous upper bound.
|
||||
* Claude's context window is 200k tokens, or 1M with extended context.
|
||||
*
|
||||
* @module utils/token-validation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum tokens allowed per session.
|
||||
* Claude's context is ~200k, so 500k is a safe upper bound for validation.
|
||||
* The largest context Claude reports today is 1M, the upper bound for validation.
|
||||
*/
|
||||
export const MAX_SESSION_TOKENS = 500_000;
|
||||
export const MAX_SESSION_TOKENS = 1_000_000;
|
||||
@@ -1637,7 +1637,7 @@
|
||||
<span class="set-row-desc">Summarizes the context at the threshold while preserving key information.</span>
|
||||
</div>
|
||||
<div class="set-row-actions">
|
||||
<input type="number" id="modalAutoCompactThreshold" class="set-num set-num-wide" value="110000" min="10000" max="500000" step="10000" onchange="app.autoSaveAutoCompact()">
|
||||
<input type="number" id="modalAutoCompactThreshold" class="set-num set-num-wide" value="110000" min="10000" max="1000000" step="10000" onchange="app.autoSaveAutoCompact()">
|
||||
<span class="set-unit">tokens</span>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="modalAutoCompactEnabled" onchange="app.autoSaveAutoCompact()"><span class="slider"></span></label>
|
||||
</div>
|
||||
@@ -1655,7 +1655,7 @@
|
||||
<span class="set-row-desc">Full context reset at the threshold. Keep it higher than auto-compact.</span>
|
||||
</div>
|
||||
<div class="set-row-actions">
|
||||
<input type="number" id="modalAutoClearThreshold" class="set-num set-num-wide" value="140000" min="10000" max="500000" step="10000" onchange="app.autoSaveAutoClear()">
|
||||
<input type="number" id="modalAutoClearThreshold" class="set-num set-num-wide" value="140000" min="10000" max="1000000" step="10000" onchange="app.autoSaveAutoClear()">
|
||||
<span class="set-unit">tokens</span>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="modalAutoClearEnabled" onchange="app.autoSaveAutoClear()"><span class="slider"></span></label>
|
||||
</div>
|
||||
|
||||
@@ -2831,7 +2831,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Populate respawn config from saved state
|
||||
this.loadSavedRespawnConfig(sessionId);
|
||||
|
||||
// Populate auto-compact/clear from session state
|
||||
// Populate auto-compact/clear from session state. A threshold above the model's window
|
||||
// can never fire, so the inputs stop there once the statusline has reported it.
|
||||
const maxThreshold = session.contextWindowSize ?? 1000000;
|
||||
document.getElementById('modalAutoCompactThreshold').max = maxThreshold;
|
||||
document.getElementById('modalAutoClearThreshold').max = maxThreshold;
|
||||
document.getElementById('modalAutoCompactEnabled').checked = session.autoCompactEnabled ?? false;
|
||||
document.getElementById('modalAutoCompactThreshold').value = session.autoCompactThreshold ?? 110000;
|
||||
document.getElementById('modalAutoCompactPrompt').value = session.autoCompactPrompt ?? '';
|
||||
|
||||
@@ -65,6 +65,13 @@ export function registerStatusTelemetryRoutes(app: FastifyInstance, ctx: Session
|
||||
session.noteReportedModel('statusline', payload?.model?.display_name);
|
||||
}
|
||||
|
||||
// The context size drives auto-compact and auto-clear. A render without one (the exporter
|
||||
// can post before the first API response) keeps the last report instead of wiping it.
|
||||
const status = reportsStatusLine ? parseSessionStatus(payload) : null;
|
||||
if (session && status?.inputTokens !== undefined) {
|
||||
session.noteContextReport({ tokens: status.inputTokens, windowSize: status.contextWindowSize });
|
||||
}
|
||||
|
||||
// Prompt-cache state of the main conversation (Claude Code v2.1.251+): kept on the
|
||||
// session for the Session Options readout and `toState()`, broadcast only when the
|
||||
// report changes (the statusline fires on every assistant message). The broadcast
|
||||
|
||||
@@ -733,6 +733,7 @@ export const StatusTelemetrySchema = z.object({
|
||||
used_percentage: z.number().nullish(),
|
||||
total_input_tokens: z.number().nullish(),
|
||||
total_output_tokens: z.number().nullish(),
|
||||
context_window_size: z.number().nullish(),
|
||||
})
|
||||
.nullish(),
|
||||
cost: z.object({ total_cost_usd: z.number().nullish() }).nullish(),
|
||||
|
||||
Reference in new issue
Block a user