mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-13 03:33:15 +08:00
fix: context-size /compact trigger, Codex marketplace plugin path, live README badges (#2237)
- suggest-compact hook now reads the latest usage record from the session transcript and suggests /compact at a window-scaled token threshold (160k/200k window, 250k/1M window; COMPACT_CONTEXT_THRESHOLD and COMPACT_CONTEXT_INTERVAL overridable), re-firing per 60k-token growth bucket; tool-call count stays as the secondary signal (#2155) - Codex repo marketplace now points at ./plugins/ecc instead of ./ — Codex never discovers plugins whose local marketplace source.path is the marketplace root (verified on Codex CLI 0.137.0); plugins/ecc is a thin folder referencing root skills/.mcp.json per maintainer direction on #2097; docs flag plugin mode as experimental with the upstream blocker openai/codex#26037 linked (#2128) - README badges for installs/stars/forks now use shields endpoint badges backed by api.ecc.tools (live install count 3,712 vs the stale static 150), which also eliminates shields' 'Unable to select next GitHub token from pool' render in the stars badge Closes #2155 Closes #2128
This commit is contained in:
@@ -11,6 +11,16 @@
|
||||
* - Strategic compacting preserves context through logical phases
|
||||
* - Compact after exploration, before execution
|
||||
* - Compact after completing a milestone, before starting next
|
||||
*
|
||||
* Two signals (#2155):
|
||||
* - Tool-call count: first at COMPACT_THRESHOLD (default 50), then every 25.
|
||||
* - Context size (primary): the latest assistant `usage` record from the
|
||||
* session transcript, compared against a window-scaled token threshold
|
||||
* (COMPACT_CONTEXT_THRESHOLD; default 160k on a 200k window, 250k on 1M),
|
||||
* re-reminding after every COMPACT_CONTEXT_INTERVAL tokens of growth
|
||||
* (default 60k). Tool count is a weak proxy for window pressure — a few
|
||||
* large reads can fill the window in very few calls, and many tiny calls
|
||||
* can cross 50 while the window is barely used.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -22,8 +32,18 @@ const {
|
||||
log,
|
||||
output
|
||||
} = require('../lib/utils');
|
||||
const {
|
||||
readLatestContextTokens,
|
||||
resolveContextWindowTokens,
|
||||
resolveContextThreshold,
|
||||
resolveContextInterval,
|
||||
computeContextBucket,
|
||||
formatWindowLabel
|
||||
} = require('../lib/transcript-context');
|
||||
|
||||
const COUNTER_FILE_PREFIX = 'claude-tool-count-';
|
||||
const CONTEXT_BUCKET_FILE_PREFIX = 'claude-context-bucket-';
|
||||
const STATE_FILE_PREFIXES = [COUNTER_FILE_PREFIX, CONTEXT_BUCKET_FILE_PREFIX];
|
||||
const DEFAULT_COMPACT_STATE_TTL_DAYS = 14;
|
||||
|
||||
function getCounterRetentionDays() {
|
||||
@@ -34,23 +54,24 @@ function getCounterRetentionDays() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweep stale counter files from the temp dir.
|
||||
* Sweep stale per-session state files from the temp dir.
|
||||
*
|
||||
* Each session writes `claude-tool-count-<sessionId>` into the OS temp
|
||||
* dir; nothing else removes them. Without a sweep these files accumulate
|
||||
* one-per-session forever. This helper removes counters whose mtime is
|
||||
* older than `retentionDays`, while preserving the active session's
|
||||
* counter (which is about to be re-written by the caller).
|
||||
* Each session writes `claude-tool-count-<sessionId>` (and, with the context
|
||||
* signal, `claude-context-bucket-<sessionId>`) into the OS temp dir; nothing
|
||||
* else removes them. Without a sweep these files accumulate one-per-session
|
||||
* forever. This helper removes state files whose mtime is older than
|
||||
* `retentionDays`, while preserving the active session's files (which are
|
||||
* about to be re-written by the caller).
|
||||
*
|
||||
* The helper never throws; per the always-exit-0 hook contract any
|
||||
* filesystem failure is swallowed and logged to stderr.
|
||||
*
|
||||
* @param {string} tempDir - The temp directory to sweep.
|
||||
* @param {number} retentionDays - Files older than this many days are removed.
|
||||
* @param {string} currentCounterFile - Absolute path of the active session's
|
||||
* counter file; preserved unconditionally.
|
||||
* @param {string[]} currentStateFiles - Absolute paths of the active session's
|
||||
* state files; preserved unconditionally.
|
||||
*/
|
||||
function cleanupOldCounters(tempDir, retentionDays, currentCounterFile) {
|
||||
function cleanupOldCounters(tempDir, retentionDays, currentStateFiles) {
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(tempDir, { withFileTypes: true });
|
||||
@@ -60,12 +81,12 @@ function cleanupOldCounters(tempDir, retentionDays, currentCounterFile) {
|
||||
}
|
||||
|
||||
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
||||
const currentBasename = path.basename(currentCounterFile);
|
||||
const currentBasenames = new Set(currentStateFiles.map(filePath => path.basename(filePath)));
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.startsWith(COUNTER_FILE_PREFIX)) continue;
|
||||
if (entry.name === currentBasename) continue;
|
||||
if (!STATE_FILE_PREFIXES.some(prefix => entry.name.startsWith(prefix))) continue;
|
||||
if (currentBasenames.has(entry.name)) continue;
|
||||
|
||||
const fullPath = path.join(tempDir, entry.name);
|
||||
let stats;
|
||||
@@ -89,43 +110,14 @@ function cleanupOldCounters(tempDir, retentionDays, currentCounterFile) {
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveSessionId() {
|
||||
// Claude Code passes hook input via stdin JSON; session_id is the
|
||||
// canonical field. Fall back to the legacy env var, then 'default'.
|
||||
try {
|
||||
const input = await readStdinJson({ timeoutMs: 1000 });
|
||||
if (input && typeof input.session_id === 'string' && input.session_id) {
|
||||
return input.session_id;
|
||||
}
|
||||
} catch {
|
||||
/* fall through to env */
|
||||
}
|
||||
return process.env.CLAUDE_SESSION_ID || 'default';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Track tool call count (increment in a temp file)
|
||||
// Use a session-specific counter file based on session ID from stdin JSON,
|
||||
// legacy env var, or 'default' as fallback.
|
||||
const rawSessionId = await resolveSessionId();
|
||||
const sessionId = rawSessionId.replace(/[^a-zA-Z0-9_-]/g, '') || 'default';
|
||||
const tempDir = getTempDir();
|
||||
const counterFile = path.join(tempDir, `${COUNTER_FILE_PREFIX}${sessionId}`);
|
||||
|
||||
// Sweep stale counter files (concern 1 of #2156). Cheap, swallows errors,
|
||||
// skips the active session's file. See cleanupOldCounters for details.
|
||||
cleanupOldCounters(tempDir, getCounterRetentionDays(), counterFile);
|
||||
|
||||
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
||||
const threshold = Number.isFinite(rawThreshold) && rawThreshold > 0 && rawThreshold <= 10000
|
||||
? rawThreshold
|
||||
: 50;
|
||||
|
||||
/**
|
||||
* Increment and persist the per-session tool-call counter.
|
||||
* Uses fd-based read+write to reduce (but not eliminate) the race window
|
||||
* between concurrent hook invocations.
|
||||
*/
|
||||
function incrementToolCallCount(counterFile) {
|
||||
let count = 1;
|
||||
|
||||
// Read existing count or start at 1
|
||||
// Use fd-based read+write to reduce (but not eliminate) race window
|
||||
// between concurrent hook invocations
|
||||
try {
|
||||
const fd = fs.openSync(counterFile, 'a+');
|
||||
try {
|
||||
@@ -150,25 +142,124 @@ async function main() {
|
||||
writeFile(counterFile, String(count));
|
||||
}
|
||||
|
||||
// Suggest compact after threshold tool calls.
|
||||
//
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the last context bucket this session already fired for (-1 when the
|
||||
* suggestion has not fired yet or the state file is unreadable/corrupted).
|
||||
*/
|
||||
function readLastContextBucket(bucketFile) {
|
||||
try {
|
||||
const parsed = parseInt(fs.readFileSync(bucketFile, 'utf8').trim(), 10);
|
||||
return Number.isInteger(parsed) && parsed >= 0 && parsed <= 1000000 ? parsed : -1;
|
||||
} catch {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the context-size suggestion when the transcript shows the session has
|
||||
* crossed into a new context bucket. Returns null when the signal is silent
|
||||
* (no transcript, below threshold, disabled, or already fired for the bucket).
|
||||
*
|
||||
* Never throws — any transcript or state-file failure silently disables the
|
||||
* signal so the hook keeps its always-exit-0 contract.
|
||||
*/
|
||||
function buildContextSuggestion(transcriptPath, bucketFile, env) {
|
||||
try {
|
||||
const usage = readLatestContextTokens(transcriptPath);
|
||||
if (!usage) return null;
|
||||
|
||||
const windowTokens = resolveContextWindowTokens(usage.tokens, usage.model);
|
||||
const threshold = resolveContextThreshold(env, windowTokens);
|
||||
if (threshold <= 0) return null; // COMPACT_CONTEXT_THRESHOLD=0 disables
|
||||
|
||||
const interval = resolveContextInterval(env);
|
||||
const bucket = computeContextBucket(usage.tokens, threshold, interval);
|
||||
if (bucket < 0) return null;
|
||||
|
||||
const lastBucket = readLastContextBucket(bucketFile);
|
||||
if (bucket <= lastBucket) return null;
|
||||
|
||||
writeFile(bucketFile, String(bucket));
|
||||
|
||||
const approxTokens = `${Math.round(usage.tokens / 1000)}k`;
|
||||
const percent = Math.round((usage.tokens / windowTokens) * 100);
|
||||
return `[StrategicCompact] Context ~${approxTokens} tokens (${percent}% of ${formatWindowLabel(windowTokens)} window) - consider /compact at the next logical boundary`;
|
||||
} catch (err) {
|
||||
log(`[StrategicCompact] Context signal skipped: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Claude Code passes hook input via stdin JSON; session_id is the
|
||||
// canonical field (legacy env var, then 'default', as fallbacks) and
|
||||
// transcript_path points at the session transcript JSONL used by the
|
||||
// context-size signal.
|
||||
let input = {};
|
||||
try {
|
||||
input = await readStdinJson({ timeoutMs: 1000 });
|
||||
} catch {
|
||||
input = {};
|
||||
}
|
||||
|
||||
const rawSessionId = (input && typeof input.session_id === 'string' && input.session_id)
|
||||
? input.session_id
|
||||
: (process.env.CLAUDE_SESSION_ID || 'default');
|
||||
const sessionId = rawSessionId.replace(/[^a-zA-Z0-9_-]/g, '') || 'default';
|
||||
const transcriptPath = (input && typeof input.transcript_path === 'string') ? input.transcript_path : '';
|
||||
|
||||
const tempDir = getTempDir();
|
||||
const counterFile = path.join(tempDir, `${COUNTER_FILE_PREFIX}${sessionId}`);
|
||||
const bucketFile = path.join(tempDir, `${CONTEXT_BUCKET_FILE_PREFIX}${sessionId}`);
|
||||
|
||||
// Sweep stale state files (concern 1 of #2156). Cheap, swallows errors,
|
||||
// skips the active session's files. See cleanupOldCounters for details.
|
||||
cleanupOldCounters(tempDir, getCounterRetentionDays(), [counterFile, bucketFile]);
|
||||
|
||||
const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
||||
const threshold = Number.isFinite(rawThreshold) && rawThreshold > 0 && rawThreshold <= 10000
|
||||
? rawThreshold
|
||||
: 50;
|
||||
|
||||
const count = incrementToolCallCount(counterFile);
|
||||
|
||||
const messages = [];
|
||||
|
||||
// Primary signal (#2155): real context size from the transcript's latest
|
||||
// usage record. Fires at a window-scaled token threshold and re-fires only
|
||||
// after the context grows by another interval step.
|
||||
const contextSuggestion = buildContextSuggestion(transcriptPath, bucketFile, process.env);
|
||||
if (contextSuggestion) {
|
||||
messages.push(contextSuggestion);
|
||||
}
|
||||
|
||||
// Secondary signal: tool-call count at threshold, then every 25 calls.
|
||||
if (count === threshold) {
|
||||
messages.push(`[StrategicCompact] ${threshold} tool calls reached - consider /compact if transitioning phases`);
|
||||
} else if (count > threshold && (count - threshold) % 25 === 0) {
|
||||
messages.push(`[StrategicCompact] ${count} tool calls - good checkpoint for /compact if context is stale`);
|
||||
}
|
||||
|
||||
// log() writes to stderr (debug log). Per the Claude Code hooks guide,
|
||||
// non-blocking PreToolUse stderr (exit 0) is only written to the debug log;
|
||||
// it does not reach the model. To inject a user-facing suggestion without
|
||||
// blocking the tool call, emit structured JSON to stdout with
|
||||
// hookSpecificOutput.additionalContext — the documented mechanism for
|
||||
// PreToolUse hooks to add context to the next model turn.
|
||||
if (count === threshold) {
|
||||
const msg = `[StrategicCompact] ${threshold} tool calls reached - consider /compact if transitioning phases`;
|
||||
log(msg);
|
||||
output({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: msg } });
|
||||
}
|
||||
|
||||
// Suggest at regular intervals after threshold (every 25 calls from threshold)
|
||||
if (count > threshold && (count - threshold) % 25 === 0) {
|
||||
const msg = `[StrategicCompact] ${count} tool calls - good checkpoint for /compact if context is stale`;
|
||||
log(msg);
|
||||
output({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: msg } });
|
||||
// PreToolUse hooks to add context to the next model turn. Hooks must emit
|
||||
// at most one stdout JSON payload per run, so both signals share it.
|
||||
if (messages.length > 0) {
|
||||
for (const msg of messages) {
|
||||
log(msg);
|
||||
}
|
||||
output({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
additionalContext: messages.join('\n')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
||||
226
scripts/lib/transcript-context.js
Normal file
226
scripts/lib/transcript-context.js
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Transcript context-size helpers for the strategic-compact hook (#2155).
|
||||
*
|
||||
* Reads the latest assistant `usage` record from a Claude Code session
|
||||
* transcript (JSONL) and derives a context-size signal:
|
||||
*
|
||||
* - `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`
|
||||
* partition the prompt, so their sum is the true context size of the turn.
|
||||
* - The context window is detected from the model id (`[1m]` marker) or from
|
||||
* the observed token count (anything above 200k implies a 1M window even
|
||||
* when logs drop the suffix).
|
||||
* - Thresholds are window-scaled and env-overridable; re-reminders fire in
|
||||
* fixed token "buckets" above the threshold so the suggestion only repeats
|
||||
* after real context growth.
|
||||
*
|
||||
* Only the tail of the transcript is read (latest records live at the end),
|
||||
* keeping the PreToolUse hook fast even for very large sessions.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const STANDARD_CONTEXT_WINDOW_TOKENS = 200000;
|
||||
const LARGE_CONTEXT_WINDOW_TOKENS = 1000000;
|
||||
const DEFAULT_CONTEXT_THRESHOLD_STANDARD = 160000;
|
||||
const DEFAULT_CONTEXT_THRESHOLD_LARGE = 250000;
|
||||
const DEFAULT_CONTEXT_INTERVAL_TOKENS = 60000;
|
||||
const DEFAULT_TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
||||
const MAX_TOKEN_SETTING = 10000000;
|
||||
const LARGE_WINDOW_MODEL_MARKER = '[1m]';
|
||||
|
||||
/**
|
||||
* Read the trailing `tailBytes` of a file as UTF-8.
|
||||
* Returns null when the file is missing or unreadable.
|
||||
*/
|
||||
function readFileTail(filePath, tailBytes) {
|
||||
let fd;
|
||||
try {
|
||||
fd = fs.openSync(filePath, 'r');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const size = fs.fstatSync(fd).size;
|
||||
const start = Math.max(0, size - tailBytes);
|
||||
const length = size - start;
|
||||
if (length <= 0) {
|
||||
return { text: '', truncated: false };
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(length);
|
||||
const bytesRead = fs.readSync(fd, buffer, 0, length, start);
|
||||
return {
|
||||
text: buffer.toString('utf8', 0, bytesRead),
|
||||
truncated: start > 0
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
fs.closeSync(fd);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the context token total from a transcript record's usage block.
|
||||
* Returns 0 when the record carries no usable usage data.
|
||||
*/
|
||||
function extractUsageTokens(record) {
|
||||
const usage = record && record.message && record.message.usage;
|
||||
if (!usage || typeof usage !== 'object') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const total =
|
||||
(Number.isFinite(usage.input_tokens) ? usage.input_tokens : 0) +
|
||||
(Number.isFinite(usage.cache_read_input_tokens) ? usage.cache_read_input_tokens : 0) +
|
||||
(Number.isFinite(usage.cache_creation_input_tokens) ? usage.cache_creation_input_tokens : 0);
|
||||
|
||||
return total > 0 ? total : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a session transcript (JSONL) backwards for the most recent record with
|
||||
* a non-empty `message.usage` block.
|
||||
*
|
||||
* @param {string} transcriptPath - Absolute path to the transcript JSONL.
|
||||
* @param {object} [options]
|
||||
* @param {number} [options.tailBytes] - How many trailing bytes to scan.
|
||||
* @returns {{ tokens: number, model: string } | null} Latest context size, or
|
||||
* null when the transcript is missing, unreadable, or has no usage records.
|
||||
*/
|
||||
function readLatestContextTokens(transcriptPath, options = {}) {
|
||||
if (typeof transcriptPath !== 'string' || !transcriptPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tailBytes = Number.isInteger(options.tailBytes) && options.tailBytes > 0
|
||||
? options.tailBytes
|
||||
: DEFAULT_TRANSCRIPT_TAIL_BYTES;
|
||||
|
||||
const tail = readFileTail(transcriptPath, tailBytes);
|
||||
if (!tail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lines = tail.text.split('\n');
|
||||
// The first line of a truncated tail is almost certainly partial JSON.
|
||||
const firstLine = tail.truncated ? 1 : 0;
|
||||
|
||||
for (let i = lines.length - 1; i >= firstLine; i--) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(line);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tokens = extractUsageTokens(record);
|
||||
if (tokens > 0) {
|
||||
const model = record.message && typeof record.message.model === 'string'
|
||||
? record.message.model
|
||||
: '';
|
||||
return { tokens, model };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the context window size for a turn.
|
||||
* 1M when the model id carries the `[1m]` marker, or when the observed token
|
||||
* count already exceeds the standard 200k window (covers logs that drop the
|
||||
* suffix); otherwise the standard 200k window.
|
||||
*/
|
||||
function resolveContextWindowTokens(tokens, model) {
|
||||
if (typeof model === 'string' && model.includes(LARGE_WINDOW_MODEL_MARKER)) {
|
||||
return LARGE_CONTEXT_WINDOW_TOKENS;
|
||||
}
|
||||
|
||||
if (Number.isFinite(tokens) && tokens > STANDARD_CONTEXT_WINDOW_TOKENS) {
|
||||
return LARGE_CONTEXT_WINDOW_TOKENS;
|
||||
}
|
||||
|
||||
return STANDARD_CONTEXT_WINDOW_TOKENS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the context-size suggestion threshold (tokens).
|
||||
* `COMPACT_CONTEXT_THRESHOLD=0` disables the context signal entirely;
|
||||
* other invalid values fall back to the window-scaled default.
|
||||
*/
|
||||
function resolveContextThreshold(env, windowTokens) {
|
||||
const raw = env && env.COMPACT_CONTEXT_THRESHOLD;
|
||||
if (raw !== undefined && raw !== null && raw !== '') {
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
if (parsed === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (Number.isInteger(parsed) && parsed > 0 && parsed <= MAX_TOKEN_SETTING) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS
|
||||
? DEFAULT_CONTEXT_THRESHOLD_LARGE
|
||||
: DEFAULT_CONTEXT_THRESHOLD_STANDARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the re-reminder step (tokens of additional context growth before
|
||||
* the suggestion repeats). Invalid values fall back to the default.
|
||||
*/
|
||||
function resolveContextInterval(env) {
|
||||
const raw = env && env.COMPACT_CONTEXT_INTERVAL;
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Number.isInteger(parsed) && parsed > 0 && parsed <= MAX_TOKEN_SETTING
|
||||
? parsed
|
||||
: DEFAULT_CONTEXT_INTERVAL_TOKENS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a context size onto a suggestion bucket.
|
||||
* Returns -1 below the threshold; bucket 0 at the threshold; +1 for every
|
||||
* `interval` tokens of growth beyond it. The hook fires only when the bucket
|
||||
* rises above the last bucket it already fired for.
|
||||
*/
|
||||
function computeContextBucket(tokens, threshold, interval) {
|
||||
if (!Number.isFinite(tokens) || threshold <= 0 || tokens < threshold) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const step = Number.isInteger(interval) && interval > 0 ? interval : DEFAULT_CONTEXT_INTERVAL_TOKENS;
|
||||
return Math.floor((tokens - threshold) / step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable label for a context window size (e.g. "200k", "1M").
|
||||
*/
|
||||
function formatWindowLabel(windowTokens) {
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS
|
||||
? '1M'
|
||||
: `${Math.round(windowTokens / 1000)}k`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
STANDARD_CONTEXT_WINDOW_TOKENS,
|
||||
LARGE_CONTEXT_WINDOW_TOKENS,
|
||||
DEFAULT_CONTEXT_THRESHOLD_STANDARD,
|
||||
DEFAULT_CONTEXT_THRESHOLD_LARGE,
|
||||
DEFAULT_CONTEXT_INTERVAL_TOKENS,
|
||||
DEFAULT_TRANSCRIPT_TAIL_BYTES,
|
||||
readLatestContextTokens,
|
||||
resolveContextWindowTokens,
|
||||
resolveContextThreshold,
|
||||
resolveContextInterval,
|
||||
computeContextBucket,
|
||||
formatWindowLabel
|
||||
};
|
||||
@@ -16,6 +16,7 @@ PLUGIN_JSON=".claude-plugin/plugin.json"
|
||||
MARKETPLACE_JSON=".claude-plugin/marketplace.json"
|
||||
CODEX_MARKETPLACE_JSON=".agents/plugins/marketplace.json"
|
||||
CODEX_PLUGIN_JSON=".codex-plugin/plugin.json"
|
||||
CODEX_MARKETPLACE_PLUGIN_JSON="plugins/ecc/.codex-plugin/plugin.json"
|
||||
OPENCODE_PACKAGE_JSON=".opencode/package.json"
|
||||
OPENCODE_PACKAGE_LOCK_JSON=".opencode/package-lock.json"
|
||||
OPENCODE_ECC_HOOKS_PLUGIN=".opencode/plugins/ecc-hooks.ts"
|
||||
@@ -270,6 +271,7 @@ update_version "$PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSIO
|
||||
update_version "$MARKETPLACE_JSON" "0,/\"version\": *\"[^\"]*\"/s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_codex_marketplace_version
|
||||
update_version "$CODEX_PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_version "$CODEX_MARKETPLACE_PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_version "$OPENCODE_PACKAGE_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_package_lock_version "$OPENCODE_PACKAGE_LOCK_JSON"
|
||||
update_opencode_hook_banner_version
|
||||
|
||||
Reference in New Issue
Block a user