mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-17 14:23:13 +08:00
fix: add context monitor cost warning opt-out
This commit is contained in:
committed by
Affaan Mustafa
parent
471dee27ec
commit
b47dfa95a3
12
README.md
12
README.md
@@ -476,6 +476,15 @@ export ECC_SESSION_START_MAX_CHARS=4000
|
||||
|
||||
# Disable SessionStart additional context entirely for low-context/local-model setups
|
||||
export ECC_SESSION_START_CONTEXT=off
|
||||
|
||||
# Keep context/scope/loop warnings but suppress API-rate cost estimates
|
||||
export ECC_CONTEXT_MONITOR_COST_WARNINGS=off
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', 'User')
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1610,6 +1619,7 @@ Add to `~/.claude/settings.json`:
|
||||
| `model` | opus | **sonnet** | ~60% cost reduction; handles 80%+ of coding tasks |
|
||||
| `MAX_THINKING_TOKENS` | 31,999 | **10,000** | ~70% reduction in hidden thinking cost per request |
|
||||
| `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` | 95 | **50** | Compacts earlier — better quality in long sessions |
|
||||
| `ECC_CONTEXT_MONITOR_COST_WARNINGS` | on | **off for subscription users** | Suppresses agent-facing API-rate estimate warnings while keeping context/scope/loop warnings |
|
||||
|
||||
Switch to Opus only when you need deep architectural reasoning:
|
||||
```
|
||||
@@ -1626,6 +1636,8 @@ Switch to Opus only when you need deep architectural reasoning:
|
||||
| `/compact` | At logical task breakpoints (research done, milestone complete) |
|
||||
| `/cost` | Monitor token spending during session |
|
||||
|
||||
If you use a Claude subscription and the context monitor's API-rate estimates are not useful, set `ECC_CONTEXT_MONITOR_COST_WARNINGS=off`. This only suppresses the agent-facing cost warnings; it does not disable context exhaustion, scope, or loop warnings.
|
||||
|
||||
### Strategic Compaction
|
||||
|
||||
The `strategic-compact` skill (included in this plugin) suggests `/compact` at logical breakpoints instead of relying on auto-compaction at 95% context. See `skills/strategic-compact/SKILL.md` for the full decision guide.
|
||||
|
||||
@@ -29,6 +29,7 @@ Add to your `~/.claude/settings.json`:
|
||||
| `model` | opus | **sonnet** | Sonnet handles ~80% of coding tasks well. Switch to Opus with `/model opus` for complex reasoning. ~60% cost reduction. |
|
||||
| `MAX_THINKING_TOKENS` | 31,999 | **10,000** | Extended thinking reserves up to 31,999 output tokens per request for internal reasoning. Reducing this cuts hidden cost by ~70%. Set to `0` to disable for trivial tasks. |
|
||||
| `CLAUDE_CODE_SUBAGENT_MODEL` | _(inherits main)_ | **haiku** | Subagents (Task tool) run on this model. Haiku is ~80% cheaper and sufficient for exploration, file reading, and test running. |
|
||||
| `ECC_CONTEXT_MONITOR_COST_WARNINGS` | on | **off for subscription users** | Suppresses agent-facing API-rate estimate warnings while keeping context exhaustion, scope, and loop warnings. |
|
||||
|
||||
### Community note on auto-compaction overrides
|
||||
|
||||
@@ -71,6 +72,22 @@ Switch models mid-session:
|
||||
| `/compact` | At logical task breakpoints (after planning, after debugging, before switching focus). |
|
||||
| `/cost` | Check token spending for the current session. |
|
||||
|
||||
### API-rate cost estimate warnings
|
||||
|
||||
ECC's context monitor can emit API-rate cost estimates from local hook telemetry. If you are on a Claude subscription and those estimates do not reflect your actual bill, disable only the agent-facing cost warnings:
|
||||
|
||||
```bash
|
||||
export ECC_CONTEXT_MONITOR_COST_WARNINGS=off
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', 'User')
|
||||
```
|
||||
|
||||
This does not disable context exhaustion warnings, scope warnings, loop warnings, `/cost`, or cost telemetry files.
|
||||
|
||||
### Strategic compaction
|
||||
|
||||
The `strategic-compact` skill (in `skills/strategic-compact/`) suggests `/compact` at logical intervals rather than relying on auto-compaction, which can trigger mid-task. See the skill's README for hook setup instructions.
|
||||
|
||||
@@ -110,6 +110,15 @@ export ECC_SESSION_START_MAX_CHARS=4000
|
||||
|
||||
# Disable SessionStart additional context entirely
|
||||
export ECC_SESSION_START_CONTEXT=off
|
||||
|
||||
# Keep context/scope/loop warnings but suppress API-rate cost estimates
|
||||
export ECC_CONTEXT_MONITOR_COST_WARNINGS=off
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', 'User')
|
||||
```
|
||||
|
||||
Profiles:
|
||||
|
||||
@@ -24,6 +24,20 @@ const LOOP_THRESHOLD = 3;
|
||||
const STALE_SECONDS = 60;
|
||||
const DEBOUNCE_CALLS = 5;
|
||||
|
||||
function isEnabledEnv(value, defaultValue = true) {
|
||||
if (value === undefined || value === null || String(value).trim() === '') {
|
||||
return defaultValue;
|
||||
}
|
||||
const normalized = String(value).trim().toLowerCase();
|
||||
if (['0', 'false', 'no', 'off', 'disabled'].includes(normalized)) return false;
|
||||
if (['1', 'true', 'yes', 'on', 'enabled'].includes(normalized)) return true;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function costWarningsEnabled(env = process.env) {
|
||||
return isEnabledEnv(env.ECC_CONTEXT_MONITOR_COST_WARNINGS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debounce state file path.
|
||||
* @param {string} sessionId
|
||||
@@ -84,7 +98,7 @@ function detectLoop(recentTools) {
|
||||
* Evaluate all warning conditions against bridge data.
|
||||
* Returns array of {severity, type, message} sorted by severity desc.
|
||||
*/
|
||||
function evaluateConditions(bridge) {
|
||||
function evaluateConditions(bridge, options = {}) {
|
||||
const warnings = [];
|
||||
const remaining = bridge.context_remaining_pct;
|
||||
|
||||
@@ -109,25 +123,27 @@ function evaluateConditions(bridge) {
|
||||
}
|
||||
|
||||
// Cost warnings
|
||||
const cost = bridge.total_cost_usd || 0;
|
||||
if (cost > COST_CRITICAL_USD) {
|
||||
warnings.push({
|
||||
severity: 3,
|
||||
type: 'cost',
|
||||
message: `COST CRITICAL: Session cost is $${cost.toFixed(2)}. ` + 'Stop and inform the user about high cost before continuing.'
|
||||
});
|
||||
} else if (cost > COST_WARNING_USD) {
|
||||
warnings.push({
|
||||
severity: 2,
|
||||
type: 'cost',
|
||||
message: `COST WARNING: Session cost is $${cost.toFixed(2)}. ` + 'Review whether the current approach justifies the expense.'
|
||||
});
|
||||
} else if (cost > COST_NOTICE_USD) {
|
||||
warnings.push({
|
||||
severity: 1,
|
||||
type: 'cost',
|
||||
message: `COST NOTICE: Session cost is $${cost.toFixed(2)}. ` + 'Consider whether the current approach is efficient.'
|
||||
});
|
||||
if (options.costWarnings !== false) {
|
||||
const cost = bridge.total_cost_usd || 0;
|
||||
if (cost > COST_CRITICAL_USD) {
|
||||
warnings.push({
|
||||
severity: 3,
|
||||
type: 'cost',
|
||||
message: `COST CRITICAL: Session cost is $${cost.toFixed(2)}. ` + 'Stop and inform the user about high cost before continuing.'
|
||||
});
|
||||
} else if (cost > COST_WARNING_USD) {
|
||||
warnings.push({
|
||||
severity: 2,
|
||||
type: 'cost',
|
||||
message: `COST WARNING: Session cost is $${cost.toFixed(2)}. ` + 'Review whether the current approach justifies the expense.'
|
||||
});
|
||||
} else if (cost > COST_NOTICE_USD) {
|
||||
warnings.push({
|
||||
severity: 1,
|
||||
type: 'cost',
|
||||
message: `COST NOTICE: Session cost is $${cost.toFixed(2)}. ` + 'Consider whether the current approach is efficient.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// File scope warning
|
||||
@@ -185,7 +201,7 @@ function run(rawInput) {
|
||||
// If bridge is stale, null out context data (still check cost/scope/loop)
|
||||
const evalBridge = isStale ? { ...bridge, context_remaining_pct: null } : bridge;
|
||||
|
||||
const warnings = evaluateConditions(evalBridge);
|
||||
const warnings = evaluateConditions(evalBridge, { costWarnings: costWarningsEnabled() });
|
||||
if (warnings.length === 0) return rawInput;
|
||||
|
||||
// Debounce logic
|
||||
@@ -239,4 +255,4 @@ if (require.main === module) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { run, evaluateConditions, detectLoop, severityLabel };
|
||||
module.exports = { run, evaluateConditions, detectLoop, severityLabel, costWarningsEnabled };
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const { run, evaluateConditions, detectLoop, severityLabel } = require('../../scripts/hooks/ecc-context-monitor');
|
||||
const { run, evaluateConditions, detectLoop, severityLabel, costWarningsEnabled } = require('../../scripts/hooks/ecc-context-monitor');
|
||||
const { getBridgePath, writeBridgeAtomic } = require('../../scripts/lib/session-bridge');
|
||||
|
||||
// Test helper
|
||||
function test(name, fn) {
|
||||
@@ -21,6 +25,18 @@ function test(name, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
function withEnv(name, value, fn) {
|
||||
const original = process.env[name];
|
||||
try {
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
return fn();
|
||||
} finally {
|
||||
if (original === undefined) delete process.env[name];
|
||||
else process.env[name] = original;
|
||||
}
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
console.log('\n=== Testing ecc-context-monitor.js ===\n');
|
||||
|
||||
@@ -113,6 +129,53 @@ function runTests() {
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('cost warnings can be suppressed without hiding context warnings', () => {
|
||||
const warnings = evaluateConditions({ total_cost_usd: 55, context_remaining_pct: 20 }, { costWarnings: false });
|
||||
assert.strictEqual(warnings.find(w => w.type === 'cost'), undefined);
|
||||
const ctx = warnings.find(w => w.type === 'context');
|
||||
assert.ok(ctx, 'Expected context warning to remain enabled');
|
||||
assert.strictEqual(ctx.severity, 3);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('ECC_CONTEXT_MONITOR_COST_WARNINGS=off disables only run-time cost warnings', () => {
|
||||
const sessionId = `ctx-monitor-cost-off-${process.pid}-${Date.now()}`;
|
||||
const input = JSON.stringify({ session_id: sessionId, tool_name: 'Bash' });
|
||||
const warnPath = path.join(os.tmpdir(), `ecc-ctx-warn-${sessionId}.json`);
|
||||
try {
|
||||
writeBridgeAtomic(sessionId, {
|
||||
context_remaining_pct: 20,
|
||||
total_cost_usd: 55,
|
||||
last_timestamp: new Date().toISOString()
|
||||
});
|
||||
const result = withEnv('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', () => JSON.parse(run(input)));
|
||||
const message = result.hookSpecificOutput.additionalContext;
|
||||
assert.ok(message.includes('CONTEXT CRITICAL'), 'Expected context warning to remain');
|
||||
assert.ok(!message.includes('COST CRITICAL'), 'Expected cost warning to be suppressed');
|
||||
} finally {
|
||||
fs.rmSync(getBridgePath(sessionId), { force: true });
|
||||
fs.rmSync(warnPath, { force: true });
|
||||
}
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test('cost warning env defaults on and accepts false-like values', () => {
|
||||
assert.strictEqual(withEnv('ECC_CONTEXT_MONITOR_COST_WARNINGS', undefined, () => costWarningsEnabled()), true);
|
||||
assert.strictEqual(withEnv('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'false', () => costWarningsEnabled()), false);
|
||||
assert.strictEqual(withEnv('ECC_CONTEXT_MONITOR_COST_WARNINGS', '0', () => costWarningsEnabled()), false);
|
||||
assert.strictEqual(withEnv('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'yes', () => costWarningsEnabled()), true);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
// evaluateConditions — scope warnings
|
||||
console.log('\nevaluateConditions (scope):');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user