diff --git a/.github/scripts/automationPolicy.test.mjs b/.github/scripts/automationPolicy.test.mjs
index fce6d3a0..8583a4ac 100644
--- a/.github/scripts/automationPolicy.test.mjs
+++ b/.github/scripts/automationPolicy.test.mjs
@@ -10,19 +10,30 @@ function read(relativePath) {
return readFileSync(resolve(REPO_ROOT, relativePath), 'utf8');
}
-test('runner attributes the tier whose reply becomes final and posts only commentBody', () => {
+test('runner attributes the selected tier and posts only the trusted comment body', () => {
const runner = read('.github/scripts/first-pass.mjs');
assert.match(runner, /const TRANSPORT = 'anthropic-messages'/);
assert.match(runner, /resolveGeneratorAttribution\(env\.CCU_BOT_GENERATOR, model, TRANSPORT\)/);
assert.match(runner, /resolveGeneratorAttribution\(env\.CCU_BOT_GENERATOR_PRO, modelPro, TRANSPORT\)/);
assert.match(runner, /parseFirstPassResponse/);
- assert.match(runner, /chooseFinalReply/);
- assert.match(runner, /formatAutomatedComment\(reply, \{ kind, generator: finalGenerator \}\)/);
+ assert.match(runner, /resolveFirstPassCandidates/);
+ assert.match(runner, /formatAutomatedComment\(selected\.reply, \{/);
+ assert.match(runner, /generator: selected\.generator/);
assert.match(runner, /JSON\.stringify\(\{\s*body:\s*commentBody\s*\}\)/);
assert.doesNotMatch(runner, /JSON\.stringify\(\{\s*body:\s*reply\s*\}\)/);
assert.doesNotMatch(runner, /CCU_BOT_TRANSPORT|prompt-injection safe|injection-safe/i);
});
+test('issue and PR opened events both use the hardened shared runner', () => {
+ const issue = read('.github/workflows/issue-first-pass.yml');
+ const pr = read('.github/workflows/pr-first-pass.yml');
+ assert.match(issue, /issues:\s*\n\s+types: \[opened\]/);
+ assert.match(pr, /pull_request_target:\s*\n\s+types: \[opened\]/);
+ for (const workflow of [issue, pr]) {
+ assert.match(workflow, /run: node \.github\/scripts\/first-pass\.mjs/);
+ }
+});
+
test('runner uses one bounded reader for AGENTS grounding and requested source', () => {
const runner = read('.github/scripts/first-pass.mjs');
assert.match(runner, /# AGENTS\.md/);
diff --git a/.github/scripts/codexWebviewServer.test.mjs b/.github/scripts/codexWebviewServer.test.mjs
new file mode 100644
index 00000000..803948f9
--- /dev/null
+++ b/.github/scripts/codexWebviewServer.test.mjs
@@ -0,0 +1,78 @@
+import * as assert from 'node:assert/strict';
+import { spawn } from 'node:child_process';
+import { get } from 'node:http';
+import { test } from 'node:test';
+import { fileURLToPath } from 'node:url';
+
+const repositoryRoot = fileURLToPath(new URL('../../', import.meta.url));
+const serverPath = fileURLToPath(
+ new URL('../../tests/ui/support/server.mjs', import.meta.url),
+);
+const uiTestPort = Number(process.env.CCU_UI_TEST_PORT ?? 4173);
+
+function request(path) {
+ return new Promise((resolve, reject) => {
+ const call = get({ hostname: '127.0.0.1', port: uiTestPort, path }, (response) => {
+ response.setEncoding('utf8');
+ let body = '';
+ response.on('data', (chunk) => { body += chunk; });
+ response.on('end', () => resolve({ status: response.statusCode, body }));
+ });
+ call.on('error', reject);
+ });
+}
+
+async function waitForServer(child, stderr) {
+ for (let attempt = 0; attempt < 50; attempt += 1) {
+ if (child.exitCode !== null) {
+ throw new Error(`UI server exited before health check: ${stderr()}`);
+ }
+ try {
+ const response = await request('/health');
+ if (response.status === 200) {
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ if (child.exitCode !== null) {
+ throw new Error(`UI server exited after health check: ${stderr()}`);
+ }
+ return;
+ }
+ } catch (error) {
+ if (child.exitCode !== null) throw error;
+ // Server startup is asynchronous; retry the real health endpoint.
+ }
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ }
+ throw new Error(`UI server health check timed out: ${stderr()}`);
+}
+
+test('UI harness returns a generic 500 body without stack or local path disclosure', async (t) => {
+ let stderr = '';
+ const child = spawn(process.execPath, [serverPath], {
+ cwd: repositoryRoot,
+ stdio: ['ignore', 'ignore', 'pipe'],
+ });
+ child.stderr.setEncoding('utf8');
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
+ t.after(() => {
+ if (child.exitCode === null) child.kill('SIGTERM');
+ });
+
+ await waitForServer(child, () => stderr);
+ await t.test('dark rendering exposes the VS Code foreground token', async () => {
+ const response = await request('/?theme=dark');
+ assert.equal(response.status, 200);
+ assert.match(response.body, /
+ comment?.user?.login === 'github-actions[bot]' &&
+ String(comment?.body || '').trimStart().startsWith(marker)
+ );
+}
+
+export async function resolveFirstPassCandidates({ cheap, pro }) {
+ let cheapCandidate;
+ try {
+ cheapCandidate = await cheap();
+ } catch {
+ cheapCandidate = null;
+ }
+
+ let proCandidate;
+ if (!cheapCandidate || needsProFirstPass(cheapCandidate)) {
+ try {
+ proCandidate = await pro(cheapCandidate);
+ } catch {
+ proCandidate = null;
+ }
+ }
+
+ try {
+ return chooseFinalReply(cheapCandidate, proCandidate);
+ } catch {
+ return null;
+ }
+}
+
export function chooseFinalReply(cheap, pro) {
if (pro && typeof pro.reply === 'string' && pro.reply.trim() !== '') {
return { reply: pro.reply.trim(), generator: pro.generator };
diff --git a/.github/scripts/first-pass.mjs b/.github/scripts/first-pass.mjs
index cbdb92ff..9fddcf0d 100644
--- a/.github/scripts/first-pass.mjs
+++ b/.github/scripts/first-pass.mjs
@@ -12,10 +12,12 @@ import {
} from 'node:fs';
import { resolve } from 'node:path';
import {
- chooseFinalReply,
createRepoReadSession,
formatAutomatedComment,
+ formatFirstPassFallback,
+ hasTrustedFirstPass,
parseFirstPassResponse,
+ resolveFirstPassCandidates,
resolveGeneratorAttribution,
validateFirstPassEnvironment,
} from './first-pass-lib.mjs';
@@ -24,6 +26,7 @@ const TRANSPORT = 'anthropic-messages';
const REPO_ROOT = resolve(process.cwd());
const env = process.env;
const base = (env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com').replace(/\/$/, '');
+const githubApi = (env.GITHUB_API_URL || 'https://api.github.com').replace(/\/$/, '');
const model = env.CCU_BOT_MODEL || 'deepseek-v4-flash';
const modelPro = env.CCU_BOT_MODEL_PRO || 'deepseek-v4-pro';
@@ -162,43 +165,65 @@ async function askModel(useModel, system, userText, think = false) {
.trim();
}
-let selected;
-try {
- const first = parseFirstPassResponse(await askModel(model, buildSystem(false), buildUser('')));
- let proCandidate;
+const commentsUrl = `${githubApi}/repos/${owner}/${repo}/issues/${num}/comments`;
+const githubHeaders = {
+ authorization: `Bearer ${env.GH_TOKEN}`,
+ accept: 'application/vnd.github+json',
+ 'content-type': 'application/json',
+ 'user-agent': 'ccu-bot',
+};
- if (!first.answerable) {
- const extra = repoReader.read(first.want_files).text;
- const second = parseFirstPassResponse(
- await askModel(modelPro, buildSystem(true), buildUser(extra), true),
- );
- proCandidate = { reply: second.reply, generator: proGenerator };
+try {
+ const response = await fetch(`${commentsUrl}?per_page=100`, { headers: githubHeaders });
+ if (!response.ok) {
+ fail(`Comment lookup failed ${response.status}: ${(await response.text()).slice(0, 300)}`);
+ }
+ if (hasTrustedFirstPass(await response.json(), kind)) {
+ console.log(`First-pass ${kind} already exists on #${num}; skipping duplicate.`);
+ process.exit(0);
}
- selected = chooseFinalReply(
- { reply: first.reply, generator: cheapGenerator },
- proCandidate,
- );
} catch (error) {
- fail(`Model call failed: ${error.message}`);
+ fail(`Comment lookup failed: ${error.message}`);
}
-const { reply, generator: finalGenerator } = selected;
+
+const selected = await resolveFirstPassCandidates({
+ cheap: async () => ({
+ ...parseFirstPassResponse(await askModel(model, buildSystem(false), buildUser(''))),
+ generator: cheapGenerator,
+ }),
+ pro: async (cheapCandidate) => {
+ const extra = repoReader.read(cheapCandidate?.want_files || []).text;
+ return {
+ ...parseFirstPassResponse(
+ await askModel(modelPro, buildSystem(true), buildUser(extra), true),
+ ),
+ generator: proGenerator,
+ };
+ },
+});
let commentBody;
-try {
- commentBody = formatAutomatedComment(reply, { kind, generator: finalGenerator });
-} catch (error) {
- fail(`First-pass formatting failed: ${error.message}`);
+if (selected) {
+ try {
+ commentBody = formatAutomatedComment(selected.reply, {
+ kind,
+ generator: selected.generator,
+ });
+ } catch (error) {
+ fail(`First-pass formatting failed: ${error.message}`);
+ }
+} else {
+ const authorWroteInChinese = /[\u3400-\u9fff]/u.test(
+ `${env.ITEM_TITLE || ''}\n${env.ITEM_BODY || ''}`,
+ );
+ commentBody = formatFirstPassFallback({ kind, isChinese: authorWroteInChinese });
+ console.warn(`Both model tiers failed to return a usable first-pass ${kind}; posting fallback.`);
}
try {
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${num}/comments`, {
+ const response = await fetch(commentsUrl, {
method: 'POST',
- headers: {
- authorization: `Bearer ${env.GH_TOKEN}`,
- accept: 'application/vnd.github+json',
- 'content-type': 'application/json',
- 'user-agent': 'ccu-bot',
- },
+ headers: githubHeaders,
body: JSON.stringify({ body: commentBody }),
});
if (!response.ok) {
diff --git a/.github/scripts/first-pass.test.mjs b/.github/scripts/first-pass.test.mjs
index 55ded193..21ac6bac 100644
--- a/.github/scripts/first-pass.test.mjs
+++ b/.github/scripts/first-pass.test.mjs
@@ -1,5 +1,6 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
+import { spawn } from 'node:child_process';
import {
mkdtempSync,
mkdirSync,
@@ -7,19 +8,166 @@ import {
symlinkSync,
writeFileSync,
} from 'node:fs';
+import { createServer } from 'node:http';
import { tmpdir } from 'node:os';
-import { join } from 'node:path';
+import { join, resolve } from 'node:path';
import {
createRepoReadSession,
chooseFinalReply,
+ formatFirstPassFallback,
formatAutomatedComment,
+ hasTrustedFirstPass,
isAllowedRepoPath,
+ needsProFirstPass,
parseFirstPassResponse,
+ resolveFirstPassCandidates,
resolveGeneratorAttribution,
validateFirstPassEnvironment,
} from './first-pass-lib.mjs';
const ANTHROPIC_MESSAGES = 'anthropic-messages';
+const REPO_ROOT = resolve(import.meta.dirname, '..', '..');
+
+async function runFallbackScenario(eventKind) {
+ const postedComments = [];
+ let modelCalls = 0;
+ const server = createServer(async (request, response) => {
+ if (request.method === 'POST' && request.url === '/v1/messages') {
+ modelCalls += 1;
+ response.writeHead(200, { 'content-type': 'application/json' });
+ response.end(JSON.stringify({ content: [] }));
+ return;
+ }
+ if (request.method === 'GET' && request.url?.endsWith('/comments?per_page=100')) {
+ response.writeHead(200, { 'content-type': 'application/json' });
+ response.end('[]');
+ return;
+ }
+ if (request.method === 'POST' && request.url?.endsWith('/comments')) {
+ const chunks = [];
+ for await (const chunk of request) chunks.push(chunk);
+ postedComments.push(JSON.parse(Buffer.concat(chunks).toString('utf8')).body);
+ response.writeHead(201, { 'content-type': 'application/json' });
+ response.end('{}');
+ return;
+ }
+ response.writeHead(404);
+ response.end();
+ });
+ await new Promise((accept) => server.listen(0, '127.0.0.1', accept));
+ const address = server.address();
+ const apiUrl = `http://127.0.0.1:${address.port}`;
+ const fixtureRoot = mkdtempSync(join(tmpdir(), 'ccu-first-pass-'));
+ const diffFile = join(fixtureRoot, 'pr.diff');
+ writeFileSync(diffFile, 'diff --git a/src/a.ts b/src/a.ts\n+export const value = 1;\n', 'utf8');
+
+ try {
+ const child = spawn(process.execPath, ['.github/scripts/first-pass.mjs'], {
+ cwd: REPO_ROOT,
+ env: {
+ ...process.env,
+ GH_TOKEN: 'test-github-token',
+ GITHUB_API_URL: apiUrl,
+ REPO: 'ClaudeCodeUsage/ClaudeCodeUsage',
+ EVENT_KIND: eventKind,
+ ITEM_NUMBER: eventKind === 'issue' ? '87' : '88',
+ ITEM_TITLE: eventKind === 'issue' ? 'High CPU usage' : 'Reduce background work',
+ ITEM_BODY: 'Reproduction details and tests are included.',
+ DIFF_FILE: eventKind === 'pr' ? diffFile : '',
+ ANTHROPIC_API_KEY: 'test-model-key',
+ ANTHROPIC_BASE_URL: apiUrl,
+ CCU_BOT_MODEL: 'deepseek-v4-flash',
+ CCU_BOT_MODEL_PRO: 'deepseek-v4-pro',
+ CCU_BOT_GENERATOR: 'deepseek',
+ CCU_BOT_GENERATOR_PRO: 'deepseek',
+ },
+ stdio: ['ignore', 'pipe', 'pipe'],
+ });
+ let stdout = '';
+ let stderr = '';
+ child.stdout.setEncoding('utf8');
+ child.stderr.setEncoding('utf8');
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
+ const exitCode = await new Promise((accept) => child.on('close', accept));
+ return { exitCode, modelCalls, postedComments, stdout, stderr };
+ } finally {
+ rmSync(fixtureRoot, { recursive: true, force: true });
+ await new Promise((accept) => server.close(accept));
+ }
+}
+
+test('shared runner posts a deterministic fallback for issue and PR model failures', async () => {
+ for (const eventKind of ['issue', 'pr']) {
+ const result = await runFallbackScenario(eventKind);
+ assert.equal(result.exitCode, 0, result.stderr);
+ assert.equal(result.modelCalls, 2);
+ assert.equal(result.postedComments.length, 1);
+ assert.match(
+ result.postedComments[0],
+ new RegExp(`^🤖 Automated first-pass ${eventKind === 'issue' ? 'reply' : 'review'}`),
+ );
+ assert.match(result.postedComments[0], /not a maintainer decision/);
+ }
+});
+
+test('empty cheap replies escalate even when the control says answerable', () => {
+ assert.equal(needsProFirstPass({ answerable: true, reply: '' }), true);
+ assert.equal(needsProFirstPass({ answerable: true, reply: 'usable' }), false);
+ assert.equal(needsProFirstPass({ answerable: false, reply: 'needs source' }), true);
+});
+
+test('static fallback is honest, English-first, and names no model provider', () => {
+ const english = formatFirstPassFallback({ kind: 'reply', isChinese: false });
+ assert.match(english, /Automated first-pass reply/);
+ assert.match(english, /did not return a usable analysis/);
+ assert.match(english, /Posted automatically by CCU Bot/);
+ assert.doesNotMatch(english, /DeepSeek|Claude|OpenAI Codex|Generated (?:with|by)/);
+
+ const bilingual = formatFirstPassFallback({ kind: 'reply', isChinese: true });
+ assert.ok(bilingual.indexOf('did not return a usable analysis') < bilingual.indexOf('自动分析服务'));
+});
+
+test('only a trusted GitHub Actions author and wrapper marker suppress a duplicate', () => {
+ const trustedBody = '🤖 Automated first-pass reply\n\nUseful body';
+ assert.equal(hasTrustedFirstPass([
+ { user: { login: 'github-actions[bot]' }, body: trustedBody },
+ ], 'reply'), true);
+ assert.equal(hasTrustedFirstPass([
+ { user: { login: 'someone' }, body: trustedBody },
+ ], 'reply'), false);
+ assert.equal(hasTrustedFirstPass([
+ { user: { login: 'github-actions[bot]' }, body: 'ordinary comment' },
+ ], 'reply'), false);
+});
+
+test('candidate resolution calls the pro tier when the cheap reply is empty', async () => {
+ const cheapGenerator = { id: 'cheap' };
+ const proGenerator = { id: 'pro' };
+ let proCalls = 0;
+ const selected = await resolveFirstPassCandidates({
+ cheap: async () => ({ reply: '', answerable: true, generator: cheapGenerator }),
+ pro: async () => {
+ proCalls += 1;
+ return { reply: 'grounded pro reply', answerable: true, generator: proGenerator };
+ },
+ });
+ assert.equal(proCalls, 1);
+ assert.deepEqual(selected, { reply: 'grounded pro reply', generator: proGenerator });
+});
+
+test('candidate resolution returns null instead of throwing when both tiers fail', async () => {
+ let proCalls = 0;
+ const selected = await resolveFirstPassCandidates({
+ cheap: async () => { throw new Error('temporary cheap failure'); },
+ pro: async () => {
+ proCalls += 1;
+ return { reply: '', answerable: true, generator: { id: 'pro' } };
+ },
+ });
+ assert.equal(proCalls, 1);
+ assert.equal(selected, null);
+});
test('current transport truthfully supports DeepSeek and Claude per tier', () => {
const cheap = resolveGeneratorAttribution(undefined, 'deepseek-v4-flash', ANTHROPIC_MESSAGES);
diff --git a/.github/scripts/verify-vsix.mjs b/.github/scripts/verify-vsix.mjs
new file mode 100644
index 00000000..6c3033cd
--- /dev/null
+++ b/.github/scripts/verify-vsix.mjs
@@ -0,0 +1,100 @@
+import { createHash } from 'node:crypto';
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import {
+ assertSafeVsixEntries,
+ assertCodexBundleMarkers,
+} from './vsixPolicy.mjs';
+
+const MAX_UNZIP_OUTPUT = 16 * 1024 * 1024;
+
+function parseRuntimeMain(main, label) {
+ if (typeof main !== 'string' || !main.startsWith('./out/') || !main.endsWith('.js') ||
+ main.includes('\\') || main.includes('\0')) {
+ throw new Error(`invalid ${label} main: ${String(main)}`);
+ }
+ const relativeMain = main.slice(2);
+ if (relativeMain.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')) {
+ throw new Error(`invalid ${label} main: ${main}`);
+ }
+ return relativeMain;
+}
+
+export function assertPackagedManifest(manifest, expectedVersion, expectedMain, entries) {
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
+ throw new Error('invalid packaged manifest');
+ }
+ if (manifest.version !== expectedVersion) {
+ throw new Error(
+ `VSIX version mismatch: expected ${expectedVersion}, got ${String(manifest.version)}`,
+ );
+ }
+
+ parseRuntimeMain(expectedMain, 'source');
+ const relativeMain = parseRuntimeMain(manifest.main, 'VSIX');
+ if (manifest.main !== expectedMain) {
+ throw new Error(
+ `VSIX main mismatch: expected ${expectedMain}, got ${manifest.main}`,
+ );
+ }
+
+ const mainEntry = `extension/${relativeMain}`;
+ if (!entries.includes(mainEntry)) {
+ throw new Error(`missing VSIX main entry: ${mainEntry}`);
+ }
+ return mainEntry;
+}
+
+function unzipText(vsixPath, entry) {
+ return execFileSync('unzip', ['-p', vsixPath, entry], {
+ encoding: 'utf8',
+ maxBuffer: MAX_UNZIP_OUTPUT,
+ });
+}
+
+export function verifyVsix(vsixPath, expectedVersion, expectedMain) {
+ const entries = execFileSync('unzip', ['-Z1', vsixPath], {
+ encoding: 'utf8',
+ maxBuffer: MAX_UNZIP_OUTPUT,
+ }).split(/\r?\n/).filter((entry) => entry.length > 0);
+
+ assertSafeVsixEntries(entries);
+ const packagedManifest = JSON.parse(unzipText(vsixPath, 'extension/package.json'));
+ const main = assertPackagedManifest(packagedManifest, expectedVersion, expectedMain, entries);
+ const javascriptEntries = entries.filter((entry) => /^extension\/out\/.*\.js$/.test(entry));
+ const bundle = javascriptEntries.map((entry) => unzipText(vsixPath, entry)).join('\n');
+ assertCodexBundleMarkers(bundle);
+
+ const bytes = readFileSync(vsixPath);
+ return {
+ path: vsixPath,
+ version: packagedManifest.version,
+ main,
+ files: entries.length,
+ bytes: bytes.length,
+ sha256: createHash('sha256').update(bytes).digest('hex'),
+ };
+}
+
+function main() {
+ const [, , requestedPath, requestedVersion] = process.argv;
+ if (!requestedPath) {
+ throw new Error('usage: node .github/scripts/verify-vsix.mjs [expected-version]');
+ }
+
+ const vsixPath = resolve(requestedPath);
+ const sourceManifest = JSON.parse(readFileSync(resolve('package.json'), 'utf8'));
+ const expectedVersion = requestedVersion ?? sourceManifest.version;
+ process.stdout.write(`${JSON.stringify(
+ verifyVsix(vsixPath, expectedVersion, sourceManifest.main),
+ null,
+ 2,
+ )}\n`);
+}
+
+if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
+ main();
+}
diff --git a/.github/scripts/vsixPolicy.mjs b/.github/scripts/vsixPolicy.mjs
new file mode 100644
index 00000000..bfe9b1ff
--- /dev/null
+++ b/.github/scripts/vsixPolicy.mjs
@@ -0,0 +1,103 @@
+const ROOT_METADATA_ENTRIES = new Set([
+ '[Content_Types].xml',
+ 'extension.vsixmanifest',
+]);
+
+const REQUIRED_ENTRIES = [
+ ...ROOT_METADATA_ENTRIES,
+ 'extension/package.json',
+ 'extension/out/extension.js',
+ 'extension/out/webview.js',
+ 'extension/out/codexView.js',
+];
+
+const FORBIDDEN_ENTRY_PATTERNS = [
+ /^extension\/(?:src|tests|docs|\.github|\.codex|\.claude|\.agents|\.superpowers)(?:\/|$)/i,
+ /^extension\/out\/test(?:\/|$)/i,
+ /^extension\/AGENTS(?:\.zh-CN)?\.md$/i,
+ /^extension\/node_modules(?:\/|$)/i,
+ /^extension\/issue87-.*\.cjs$/i,
+ /^extension\/out\/claudeIncrementalPrototype\.js$/i,
+ /(?:^|\/)\.env(?:\.|$)/i,
+ /\.(?:pem|key|p12|pfx)$/i,
+];
+
+const REQUIRED_BUNDLE_MARKERS = [
+ { label: "provider === 'codex'", present: (bundle) => bundle.includes("provider === 'codex'") },
+ { label: 'renderTodayData(provider)', present: (bundle) => bundle.includes('renderTodayData(provider)') },
+ { label: 'renderSessionData(provider)', present: (bundle) => bundle.includes('renderSessionData(provider)') },
+ { label: 'renderSettingsPanel(provider)', present: (bundle) => bundle.includes('renderSettingsPanel(provider)') },
+ { label: 'data-provider-target', present: (bundle) => bundle.includes('data-provider-target') },
+];
+
+const RETIRED_BUNDLE_MARKERS = [
+ 'ccu.codex.ui.v1',
+ 'data-codex-tab-button="recent"',
+ 'data-codex-tab-button="7d"',
+ 'data-codex-tab-button="30d"',
+ 'data-codex-tab-button="all"',
+ 'data-codex-tab-button="threads"',
+ 'data-codex-tab-button="projects"',
+ 'data-codex-tab-button="behavior"',
+ 'data-codex-tab-button="settings"',
+ 'data-codex-page="overview"',
+ 'data-codex-page="explore"',
+ 'data-codex-page="recommendations"',
+ 'data-codex-action',
+ 'hostState.codexUi = nextState',
+];
+
+function assertCanonicalEntry(entry) {
+ if (typeof entry !== 'string' || entry.length === 0 || entry.includes('\0') ||
+ entry.includes('\\') || entry.startsWith('/') || /^[A-Za-z]:/.test(entry) ||
+ entry.endsWith('/')) {
+ throw new Error(`unsafe VSIX entry: ${String(entry)}`);
+ }
+ if (entry.split('/').some((segment) => segment === '' || segment === '.' || segment === '..')) {
+ throw new Error(`unsafe VSIX entry: ${entry}`);
+ }
+ if (!ROOT_METADATA_ENTRIES.has(entry) && !entry.startsWith('extension/')) {
+ throw new Error(`unsafe VSIX entry: ${entry}`);
+ }
+}
+
+export function assertSafeVsixEntries(entries) {
+ if (!Array.isArray(entries)) {
+ throw new Error('VSIX entries must be an array');
+ }
+
+ const seen = new Set();
+ for (const entry of entries) {
+ assertCanonicalEntry(entry);
+ const collisionKey = entry.toLowerCase();
+ if (seen.has(collisionKey)) {
+ throw new Error(`duplicate VSIX entry: ${entry}`);
+ }
+ seen.add(collisionKey);
+ if (FORBIDDEN_ENTRY_PATTERNS.some((pattern) => pattern.test(entry))) {
+ throw new Error(`forbidden VSIX entry: ${entry}`);
+ }
+ }
+
+ for (const required of REQUIRED_ENTRIES) {
+ if (!seen.has(required.toLowerCase())) {
+ throw new Error(`missing required VSIX entry: ${required}`);
+ }
+ }
+}
+
+export function assertCodexBundleMarkers(bundle) {
+ if (typeof bundle !== 'string') {
+ throw new Error('bundle must be text');
+ }
+ for (const marker of REQUIRED_BUNDLE_MARKERS) {
+ if (!marker.present(bundle)) {
+ throw new Error(`missing bundle marker: ${marker.label}`);
+ }
+ }
+ for (const marker of RETIRED_BUNDLE_MARKERS) {
+ if (bundle.includes(marker)) {
+ throw new Error(`retired bundle marker: ${marker}`);
+ }
+ }
+}
diff --git a/.github/scripts/vsixPolicy.test.mjs b/.github/scripts/vsixPolicy.test.mjs
new file mode 100644
index 00000000..8b95bf91
--- /dev/null
+++ b/.github/scripts/vsixPolicy.test.mjs
@@ -0,0 +1,278 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+
+import {
+ assertSafeVsixEntries,
+ assertCodexBundleMarkers,
+} from './vsixPolicy.mjs';
+import { assertPackagedManifest } from './verify-vsix.mjs';
+
+const minimalEntries = [
+ '[Content_Types].xml',
+ 'extension.vsixmanifest',
+ 'extension/package.json',
+ 'extension/out/extension.js',
+ 'extension/out/webview.js',
+ 'extension/out/codexView.js',
+];
+
+const validBundle = [
+ "provider === 'codex'",
+ 'renderTodayData(provider)',
+ 'renderSessionData(provider)',
+ 'renderSettingsPanel(provider)',
+ 'data-provider-target',
+].join('\n');
+
+const sourceMain = './out/extension.js';
+
+test('VSIX policy accepts runtime files and normal publication assets', () => {
+ assert.doesNotThrow(() => assertSafeVsixEntries([
+ ...minimalEntries,
+ 'extension/README.md',
+ 'extension/CHANGELOG.md',
+ 'extension/LICENSE',
+ 'extension/icon.png',
+ 'extension/package.nls.json',
+ ]));
+});
+
+test('VSIX policy rejects development, dependency, and secret material', () => {
+ for (const entry of [
+ 'extension/src/webview.ts',
+ 'extension/out/test/codexView.test.js',
+ 'extension/tests/ui/codex-visual.spec.mjs',
+ 'extension/docs/private.md',
+ 'extension/.github/workflows/test.yml',
+ 'extension/.codex/private.json',
+ 'extension/AGENTS.md',
+ 'extension/node_modules/left-pad/index.js',
+ 'extension/node_modules/@playwright/test/index.js',
+ 'extension/issue87-benchmark.cjs',
+ 'extension/issue87-prototype-benchmark.cjs',
+ 'extension/out/claudeIncrementalPrototype.js',
+ 'extension/.env.local',
+ 'extension/private.pem',
+ 'extension/private.key',
+ ]) {
+ assert.throws(
+ () => assertSafeVsixEntries([...minimalEntries, entry]),
+ /forbidden VSIX entry/,
+ entry,
+ );
+ }
+});
+
+test('VSIX policy rejects internal Superpowers review material', () => {
+ assert.throws(
+ () => assertSafeVsixEntries([
+ ...minimalEntries,
+ 'extension/.superpowers/sdd/review-a45ff9e..51ad1d0.diff',
+ ]),
+ /forbidden VSIX entry/,
+ );
+});
+
+test('VSIX policy rejects unsafe or non-canonical archive paths', () => {
+ for (const entry of [
+ '/extension/out/extra.js',
+ 'C:\\extension\\out\\extra.js',
+ '\\\\server\\share\\extra.js',
+ 'extension\\out\\extra.js',
+ 'extension/out/evil\0.js',
+ 'extension/./out/extra.js',
+ 'extension/out/../private.json',
+ 'extension/out/',
+ '../extension/out/extra.js',
+ 'unexpected-root-file.txt',
+ '_rels/.rels',
+ ]) {
+ assert.throws(
+ () => assertSafeVsixEntries([...minimalEntries, entry]),
+ /unsafe VSIX entry/,
+ entry,
+ );
+ }
+});
+
+test('VSIX policy rejects archive paths with empty segments', () => {
+ for (const entry of [
+ 'extension//.superpowers/sdd/private.md',
+ 'extension/out//test/private.js',
+ 'extension//node_modules/pkg/index.js',
+ ]) {
+ assert.throws(
+ () => assertSafeVsixEntries([...minimalEntries, entry]),
+ /unsafe VSIX entry/,
+ entry,
+ );
+ }
+});
+
+test('VSIX policy rejects malformed entry inventories', () => {
+ assert.throws(() => assertSafeVsixEntries(null), /VSIX entries must be an array/);
+ assert.throws(() => assertSafeVsixEntries([...minimalEntries, 42]), /unsafe VSIX entry/);
+ assert.throws(() => assertSafeVsixEntries([...minimalEntries, '']), /unsafe VSIX entry/);
+});
+
+test('VSIX policy rejects duplicate archive entries', () => {
+ assert.throws(
+ () => assertSafeVsixEntries([...minimalEntries, 'extension/icon.png', 'extension/icon.png']),
+ /duplicate VSIX entry: extension\/icon\.png/,
+ );
+});
+
+test('VSIX policy requires the manifest and basic runtime bundles', () => {
+ for (const required of [
+ '[Content_Types].xml',
+ 'extension.vsixmanifest',
+ 'extension/package.json',
+ 'extension/out/extension.js',
+ 'extension/out/webview.js',
+ 'extension/out/codexView.js',
+ ]) {
+ assert.throws(
+ () => assertSafeVsixEntries(minimalEntries.filter((entry) => entry !== required)),
+ new RegExp(`missing required VSIX entry: ${required.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`),
+ required,
+ );
+ }
+});
+
+test('bundle policy requires current Codex product and host-state markers', () => {
+ assert.doesNotThrow(() => assertCodexBundleMarkers(validBundle));
+ for (const marker of [
+ "provider === 'codex'",
+ 'renderTodayData(provider)',
+ 'renderSessionData(provider)',
+ 'renderSettingsPanel(provider)',
+ 'data-provider-target',
+ ]) {
+ assert.throws(
+ () => assertCodexBundleMarkers(validBundle.replace(marker, '')),
+ /missing bundle marker/,
+ marker,
+ );
+ }
+});
+
+test('bundle policy rejects retired flat tabs and legacy Codex storage', () => {
+ for (const marker of [
+ 'ccu.codex.ui.v1',
+ 'data-codex-tab-button="recent"',
+ 'data-codex-tab-button="7d"',
+ 'data-codex-tab-button="30d"',
+ 'data-codex-tab-button="all"',
+ 'data-codex-tab-button="threads"',
+ 'data-codex-tab-button="projects"',
+ 'data-codex-tab-button="behavior"',
+ 'data-codex-tab-button="settings"',
+ 'data-codex-page="overview"',
+ 'data-codex-page="explore"',
+ 'data-codex-page="recommendations"',
+ 'data-codex-action',
+ 'hostState.codexUi = nextState',
+ ]) {
+ assert.throws(
+ () => assertCodexBundleMarkers(`${validBundle}\n${marker}`),
+ /retired bundle marker/,
+ marker,
+ );
+ }
+});
+
+test('bundle policy rejects non-text input', () => {
+ assert.throws(() => assertCodexBundleMarkers(null), /bundle must be text/);
+});
+
+test('packaged manifest policy returns its verified runtime main entry', () => {
+ assert.equal(
+ assertPackagedManifest(
+ { version: '2.1.1', main: './out/extension.js' },
+ '2.1.1',
+ sourceMain,
+ minimalEntries,
+ ),
+ 'extension/out/extension.js',
+ );
+});
+
+test('packaged manifest policy rejects a runtime main that differs from the source manifest', () => {
+ assert.throws(
+ () => assertPackagedManifest(
+ { version: '2.1.1', main: './out/evil.js' },
+ '2.1.1',
+ sourceMain,
+ [...minimalEntries, 'extension/out/evil.js'],
+ ),
+ /VSIX main mismatch/,
+ );
+});
+
+test('packaged manifest policy rejects version mismatches', () => {
+ assert.throws(
+ () => assertPackagedManifest(
+ { version: '9.9.9', main: './out/extension.js' },
+ '2.1.1',
+ sourceMain,
+ minimalEntries,
+ ),
+ /VSIX version mismatch: expected 2\.1\.1, got 9\.9\.9/,
+ );
+});
+
+test('packaged manifest policy rejects unsafe or missing runtime mains', () => {
+ for (const main of [
+ '',
+ '/out/extension.js',
+ 'C:\\out\\extension.js',
+ '.\\out\\extension.js',
+ './out/../private.js',
+ './out//extension.js',
+ './README.md',
+ ]) {
+ assert.throws(
+ () => assertPackagedManifest(
+ { version: '2.1.1', main },
+ '2.1.1',
+ sourceMain,
+ minimalEntries,
+ ),
+ /invalid VSIX main|missing VSIX main entry/,
+ main,
+ );
+ }
+
+ assert.throws(
+ () => assertPackagedManifest(
+ { version: '2.1.1', main: './out/missing.js' },
+ '2.1.1',
+ './out/missing.js',
+ minimalEntries,
+ ),
+ /missing VSIX main entry: extension\/out\/missing\.js/,
+ );
+});
+
+test('packaged manifest policy rejects an invalid source runtime main', () => {
+ for (const expectedMain of [
+ undefined,
+ 42,
+ '',
+ '/out/extension.js',
+ './out/../extension.js',
+ './out//extension.js',
+ './README.md',
+ ]) {
+ assert.throws(
+ () => assertPackagedManifest(
+ { version: '2.1.1', main: './out/extension.js' },
+ '2.1.1',
+ expectedMain,
+ minimalEntries,
+ ),
+ /invalid source main/,
+ String(expectedMain),
+ );
+ }
+});
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 31a69569..718085af 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -86,11 +86,14 @@ jobs:
run: npm run compile
- name: Package .vsix
- run: npx -y @vscode/vsce package --out claude-code-usage.vsix
+ run: npx -y @vscode/vsce@3.9.1 package --out claude-code-usage.vsix
+
+ - name: Verify .vsix
+ run: node .github/scripts/verify-vsix.mjs claude-code-usage.vsix
- name: Publish to VS Code Marketplace
if: github.event_name == 'release' || inputs.publish_vscode
- run: npx -y @vscode/vsce publish --packagePath claude-code-usage.vsix --pat "$VSCE_PAT"
+ run: npx -y @vscode/vsce@3.9.1 publish --packagePath claude-code-usage.vsix --pat "$VSCE_PAT"
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3c45d1d3..45f31b9c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,15 +1,12 @@
-# Run the test suite on every push and pull request.
+# Run the release-quality gates on every push and pull request.
#
# What this does:
-# 1. Checks out the commit.
-# 2. Installs dependencies (npm ci).
-# 3. Runs `npm test`, which compiles TypeScript and executes the
-# node:test suite against the compiled output in out/test/.
+# 1. Runs the compiled Node logic and repository-policy suite.
+# 2. Runs the complete Webview gate in the canonical Playwright container.
+# 3. Packages and verifies a smoke VSIX only after both test jobs pass.
#
-# A red check here means a regression in the pure-logic modules (pricing /
-# aggregation / quota / i18n) — visible on the PR before merge. To make it
-# actually block merging, add "Test" as a required status check under the
-# repo's branch-protection rules (Settings → Branches).
+# To block merging, require all three checks under the repository's branch
+# protection rules: Test / Node Tests, Test / UI Test, and Test / Release Package.
name: Test
@@ -24,19 +21,62 @@ permissions:
jobs:
test:
+ name: Node Tests
runs-on: ubuntu-latest
+ timeout-minutes: 12
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
-
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'npm'
+ - name: Install dependencies
+ run: npm ci
+ - name: Run Node tests
+ run: npm test
+ ui:
+ name: UI Test
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ container:
+ image: mcr.microsoft.com/playwright:v1.61.1-noble
+ options: --ipc=host
+ env:
+ PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
+ steps:
+ - name: Checkout
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ - name: Setup Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: '20'
+ cache: 'npm'
- name: Install dependencies
run: npm ci
+ - name: Run full Webview gate
+ run: npm run test:ui
- - name: Run tests
- run: npm test
+ package:
+ name: Release Package
+ needs: [test, ui]
+ runs-on: ubuntu-latest
+ timeout-minutes: 12
+ steps:
+ - name: Checkout
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ - name: Setup Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ - name: Install dependencies
+ run: npm ci
+ - name: Compile
+ run: npm run compile
+ - name: Package smoke VSIX
+ run: npx -y @vscode/vsce@3.9.1 package --out /tmp/claude-code-usage-ci.vsix
+ - name: Verify smoke VSIX
+ run: node .github/scripts/verify-vsix.mjs /tmp/claude-code-usage-ci.vsix
diff --git a/.gitignore b/.gitignore
index 07d38c3c..f55f9cd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,15 @@
out
node_modules
.vscode-test/
+test-results/
+playwright-report/
*.vsix
.DS_Store
+# Linux Chromium images are the canonical CI baselines. Playwright creates
+# host-specific bootstrap images during local macOS visual review.
+tests/ui/codex-visual.spec.mjs-snapshots/*-chromium-darwin.png
+
# Secrets — belt-and-braces. Source never reads these,
# but keep accidental drops out of git.
.env
diff --git a/.superpowers/sdd/ui-task-3-report.md b/.superpowers/sdd/ui-task-3-report.md
new file mode 100644
index 00000000..16328ed8
--- /dev/null
+++ b/.superpowers/sdd/ui-task-3-report.md
@@ -0,0 +1,105 @@
+# UI Task 3 — Codex usage overview
+
+## RED / GREEN evidence
+
+- RED: added focused limit, formatter, usage-view, and Overview renderer tests
+ before the production modules. The required compile/test command failed with
+ the expected missing `codexLimits` / `codexFormat` module errors and absent
+ classified-limit and safe task-identity fields.
+- GREEN: the focused suite passed after adding the pure limit and formatter
+ modules, exposing classified view data from `buildCodexUsageView`, and
+ rebuilding only the Overview interior as limits → recent task → trend.
+
+## Interfaces and decisions
+
+- `codexLimits.ts` classifies each locally observed window as `current`,
+ `expired`, `missing`, or `unlimited`. A 0% window with a future reset stays
+ current; an observation without a reset is stale/expired rather than being
+ presented as indefinitely current.
+- `codexFormat.ts` owns side-effect-free locale-aware duration, relative-time,
+ and byte formatting. 300-minute and 10080-minute labels remain product copy
+ (5-hour/weekly); other windows use the duration formatter.
+- `CodexUsageView.limits` is renderer-ready. The legacy `limit` field remains
+ only for the status bar, whose existing current-window behavior is unchanged.
+- Recent-task DOM values use deterministic synthetic `task-*` and `project-*`
+ keys. No raw session or project ID is rendered. Duration now truthfully says
+ “Observed session duration total (proxy)” (and localized equivalents).
+- The Overview contains exactly one trend module with declarative scope,
+ metric, and date-drilldown actions. It deliberately adds no controller;
+ interaction wiring remains UI Task 6.
+- Token composition remains three additive segments (fresh input, cached input,
+ output). Reasoning is shown only as an output subset.
+
+## Verification
+
+Focused command:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/codexLimits.test.js out/test/codexFormat.test.js out/test/codexUsage.test.js out/test/codexView.test.js
+```
+
+Result: 36 passed, 0 failed.
+
+Full command:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: 344 total; 343 passed, 0 failed, 1 skipped (the existing filesystem
+mtime capability fixture).
+
+`git diff --check` also passed.
+
+## ⚠️ Concerns
+
+- No Extension Development Host smoke test was run: this task intentionally
+ emits only declarative Overview actions and leaves the controller for UI Task
+ 6. The pure renderer, compile, and full Node suite are verified.
+
+## Review follow-up
+
+### RED / GREEN evidence
+
+- RED: production-path formatter and source-copy tests were added before the
+ implementation. Compilation failed at the four expected references: the
+ missing `createCodexLocalizedFormatters` export in two test modules and the
+ missing `accountSnapshotLastObserved` copy key in the locale-completeness
+ test and its index expression.
+- GREEN: the focused suite now passes 44/44. Its production-shaped
+ `de-DE` + `Asia/Hong_Kong` fixture verifies localized date/time, duration,
+ relative countdown, and byte rendering together.
+- Recent rooted-task identity is now anchored only to the canonical root's
+ pseudonymous session key. Appending a child leaves both rendered task and
+ project keys unchanged; reordered cycle and parentless-orphan fixtures prove
+ deterministic safe fallbacks without exposing raw session or project keys.
+- Limit source copy now distinguishes local-log observations from OAuth
+ account snapshots. Both sources have renderer coverage, and locale
+ completeness covers all eight UI locales.
+
+### Verification
+
+Focused command:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/codexFormat.test.js out/test/codexLimits.test.js out/test/codexUsage.test.js out/test/codexView.test.js out/test/providerSelection.test.js
+```
+
+Result: 44 passed, 0 failed.
+
+Full command:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: 348 total; 347 passed, 0 failed, 1 skipped (the existing filesystem
+mtime capability fixture).
+
+`git diff --check` passed after the review changes.
+
+### ⚠️ Concerns
+
+- The Extension Development Host smoke test remains deferred to the controller
+ integration task; the production formatter wiring, pure renderer, compile,
+ focused suite, and full Node suite are verified here.
diff --git a/.superpowers/sdd/ui-task-4-report.md b/.superpowers/sdd/ui-task-4-report.md
new file mode 100644
index 00000000..11ea0ba4
--- /dev/null
+++ b/.superpowers/sdd/ui-task-4-report.md
@@ -0,0 +1,161 @@
+# UI Task 4 — Codex Explore
+
+## RED / GREEN evidence
+
+- RED: added focused identity, usage-view, and Explore renderer tests before
+ production changes. The required compile command failed at the intended
+ missing contracts: `stableCodexViewKey`, thread/project `viewKey` lineage
+ fields, the tree/flat Explore state contract, and the renderer filter input.
+- GREEN: after the minimal view-model and renderer implementation, the focused
+ Codex identity/usage/view suite passed 52/52.
+
+## Interfaces and decisions
+
+- `stableCodexViewKey()` SHA-256 hashes an existing pseudonymous identity key
+ and truncates it to 16 hexadecimal characters. Codex Explore DOM attributes,
+ declarative actions, and filter state use that `viewKey`, never a session key,
+ project key, raw path, or repository URL.
+- Thread rows now carry `viewKey`, optional `parentViewKey`,
+ `rootTaskViewKey`, and `depth`. The lineage walk supports three or more
+ levels; a cycle or missing parent becomes a neutral top-level row without a
+ parent identifier/title.
+- Projects sort by their last lineage activity. Their expanded summary shows
+ the latest 20 sessions and a truthful `shown/total` count.
+- Explore has exactly Projects, Sessions, and Models & effort internal views.
+ Sessions declares a tree layout by default and a flat layout whenever a
+ query/filter is active. The renderer adds labelled controls, live count,
+ accessible details content, filter chips, and declarative-only actions; Task
+ 6 remains responsible for controller/event wiring.
+- Models & effort keeps the established section header, composition, scope,
+ and model-table design. No provider cost is invented.
+
+## Verification
+
+Focused:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/codexIdentity.test.js out/test/codexUsage.test.js out/test/codexView.test.js
+```
+
+Result: 52 passed, 0 failed.
+
+Full:
+
+```sh
+env PATH=/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin:$PATH node --run compile && /Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: 354 total; 353 passed, 0 failed, 1 skipped (existing filesystem mtime
+capability fixture). `git diff --check` passed.
+
+## ⚠️ Concerns
+
+- No Extension Development Host smoke test was run. This task intentionally
+ emits declarative Explore actions only; controller wiring is reserved for
+ Task 6.
+
+## Independent-review hardening
+
+### RED / GREEN evidence
+
+- RED: 12 review-focused tests were added for the runtime identity contract,
+ persisted legacy-key sanitization, verified period membership/availability,
+ project previews beyond the global 1,000-row cap, ARIA/hidden tabs,
+ controlled filters, orphan/cycle status, complete mobile facts, and an empty
+ recent Models scope. Before production changes, TypeScript reported the 10
+ intentionally missing contracts. After the data-layer changes, 5 tests
+ passed and the 7 renderer tests remained RED.
+- GREEN: the four focused files (`codexIdentity`, `codexIndex`, `codexUsage`,
+ and `codexView`) pass 93/93. The persistence AST-policy regression found by
+ the first full run was fixed by making the same lowercase 64-hex validation
+ explicit at the index persistence boundary; the policy/index/identity subset
+ then passed 61/61.
+
+### Review decisions
+
+- Pseudonymous identity keys now have a branded type plus a runtime parser.
+ `stableCodexViewKey()` rejects anything except exactly 64 lowercase
+ hexadecimal characters, so a raw ID, path, URL, short key, or uppercase key
+ cannot be silently hashed into public state. Invalid persisted session keys
+ degrade to a fixed internal pseudonym; invalid parent/project keys are
+ omitted. No invalid raw value is re-hashed.
+- Session period membership is derived only from the file's verified
+ `period.days` in the snapshot timezone. Rolling/all-time filters are disabled
+ unless their coverage is complete, and the no-filter option is the empty
+ value so the real `all` period occurs exactly once.
+- Missing-parent and cycle rows retain distinct neutral status attributes while
+ exposing no identifier. Flat results display the localized parent-task label
+ with the localized unavailable value.
+- Project previews are selected from each project's complete contribution set,
+ independently of the global 1,000-session display cap. Mobile details repeat
+ every desktop fact through labelled `dt`/`dd` pairs and the injected
+ formatters.
+- Explore and Models & effort now render complete ARIA tab relationships and
+ initially hide every inactive panel. The recent Models panel reports a real
+ empty state and never substitutes seven-day totals.
+
+### Final verification
+
+Focused:
+
+```sh
+env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' node_modules/typescript/bin/tsc -p ./ && env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' --test out/test/codexIdentity.test.js out/test/codexIndex.test.js out/test/codexUsage.test.js out/test/codexView.test.js
+```
+
+Result: 93 passed, 0 failed.
+
+Full:
+
+```sh
+env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: 366 total; 365 passed, 0 failed, 1 skipped (the existing filesystem
+mtime capability fixture). No Extension Development Host smoke test was run.
+
+## Final-review follow-up
+
+### RED / GREEN evidence
+
+- RED: three focused regressions were added before the fixes. All three failed
+ for the intended behavior gaps: a distinct local directory was absent from
+ mobile facts, a stale incomplete `7d` request rendered selected/disabled with
+ a period chip and flat layout while showing every row, and German still
+ inherited English `sessions` copy.
+- GREEN: the three new regressions pass 3/3. The affected renderer/localization
+ focused suite passes 34/34.
+
+### Final-review decisions
+
+- Mobile session facts conditionally include the same localized local-directory
+ label and value as desktop whenever the directory differs from the displayed
+ project name. Both surfaces use the shared HTML escaping path; the regression
+ uses markup-like input and verifies that only escaped text is emitted.
+- A requested period is now canonicalized against both the allowed period set
+ and `sessionPeriodAvailability` before result filtering, chips, selected
+ options, layout, visible count, and clear-button state are calculated. A stale
+ incomplete period therefore becomes the coherent no-period-filter state;
+ the unavailable option remains disabled but is not selected.
+- `sessions`, `modelsEffort`, `clearFilters`, and `activeFilters` are required
+ `CodexViewCopy` fields. Every non-English UI locale supplies natural copy,
+ and the locale completeness test rejects English fallback separately for
+ each field.
+
+### Follow-up verification
+
+Focused:
+
+```sh
+env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' node_modules/typescript/bin/tsc -p ./ && env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' --test out/test/codexView.test.js out/test/providerSelection.test.js
+```
+
+Result: 34 passed, 0 failed.
+
+Full:
+
+```sh
+env ELECTRON_RUN_AS_NODE=1 '/Applications/Visual Studio Code.app/Contents/MacOS/Code' --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: 368 total; 367 passed, 0 failed, 1 skipped (the existing filesystem
+mtime capability fixture). No Extension Development Host smoke test was run.
diff --git a/.superpowers/sdd/ui-task-5-report.md b/.superpowers/sdd/ui-task-5-report.md
new file mode 100644
index 00000000..c46301be
--- /dev/null
+++ b/.superpowers/sdd/ui-task-5-report.md
@@ -0,0 +1,119 @@
+# UI Task 5 report: evidence-driven Codex recommendations
+
+Status: **DONE**
+
+## RED evidence
+
+Tests were written before the recommendation implementation in:
+
+- `src/test/codexInsights.test.ts`
+- `src/test/codexView.test.ts`
+
+Bundled Node was used because `npm` is unavailable in this environment:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ node_modules/typescript/bin/tsc -p ./
+```
+
+The first RED compile failed as intended: the old implementation accepted only
+one `buildCodexInsights` argument, exposed the old
+`post-patch-tool-call-intensity` kind, and lacked `scope` on `CodexInsight`.
+No production change preceded that failure.
+
+## GREEN implementation
+
+- Replaced the advice contract with the five approved neutral kinds and the
+ allowlisted numeric/aggregate evidence keys. Every insight has `proxy: true`.
+- Empty evidence produces `[]`; `pasteReadyConstraint([])` is empty and
+ non-empty constraints are only the sentences for the actual triggered kinds.
+- Effort insight only proposes a representative-task A/B comparison. Cache and
+ structural signals are explicitly proxies, not file, command, test, or review
+ counts; no conversation body, tool arguments, path, cost, or other content is
+ read by the recommendation code.
+- Recommendations render role/model/effort composition and proxy KPIs, then
+ cards in the fixed scope → observation → readable evidence → proxy note →
+ conditional-action order. Internal evidence keys are never rendered.
+- The declarative `set-recommendation-scope` selector disables partial 7d/30d
+ ranges with an adjacent migration note. Recent and aggregate all-time remain
+ available. Complete scopes with no insight show an accurate empty state.
+- Added Codex copy fields plus an eight-locale completeness test; existing
+ locale override titles use the new kinds.
+
+## Verification
+
+Focused GREEN command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ node_modules/typescript/bin/tsc -p ./ && \
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/codexInsights.test.js out/test/codexUsage.test.js \
+ out/test/providerSelection.test.js out/test/codexView.test.js
+```
+
+Initial Task 5 result: **66 passed, 0 failed**.
+
+Full-suite command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ node_modules/typescript/bin/tsc -p ./ && \
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Initial Task 5 result: **371 passed, 0 failed, 1 skipped**. The skip is the pre-existing
+filesystem capability case that cannot preserve an exact inode mtime.
+
+`git diff --check` passed.
+
+## Threshold semantics
+
+- Multi-agent: at least 3 child threads and ≥40% fresh share.
+- Approval reviewer: ≥20% approval-reviewer fresh share.
+- Effort comparison: observed high effort in a 1–2 patch structural proxy
+ scope, except where the multi-agent shape already explains the scope.
+- Post-patch proxy: ≥5 post-patch tool calls or ≥3 tool calls per patch.
+- Cache/context: processed-to-fresh ratio ≥2.5.
+
+These are neutral evidence thresholds, not causal, cost, efficiency, file,
+command, test, or review claims.
+
+## Review follow-up
+
+Review tests were added before the follow-up implementation. The first focused
+RED compile failed because `buildScopedCodexInsights` did not yet exist. A later
+locale RED failed when `de-DE.recommendations` still fell back to English, and
+the copy-policy RED failed while the legacy generic constraint keys remained in
+the merged user-facing object.
+A final empty-state RED caught stale scoped insights surviving a refresh error
+and a null webview view before both boundaries were normalized.
+
+- Added one shared `CodexScopedInsights` host/render contract and the pure
+ `buildScopedCodexInsights(view)` helper. The production fixture returns
+ `recent=1`, `7d=1`, `30d=2`, and `all=1`; every item carries its explicit
+ scope. Extension/webview initial, disabled, unavailable, refresh-error, and
+ empty states use a four-empty-scope object.
+- The deprecated one-argument builder now fails closed for every object with
+ `periodCoverage`, including complete coverage. Production rolling callers
+ pass `7d` or `30d` explicitly.
+- Every complete recommendation panel renders its own scoped constraint; a
+ partial rolling panel renders neither cards nor a constraint.
+- Composition now gives comparable numeric fresh totals and shares for root,
+ child, approval-reviewer, model, and effort dimensions, without cost claims.
+- All seven non-English locales provide localized values for the 13 relevant
+ scalar fields, five observations, five tips, and 15 evidence labels (their
+ existing five titles remain localized). Deep merge preserves complete maps,
+ and legacy generic test/stop constraints are stripped from the user-facing
+ copy object.
+
+Review focused result: **71 passed, 0 failed**.
+
+Review full-suite result: **376 passed, 0 failed, 1 skipped**. The skip remains
+the pre-existing exact-inode-mtime filesystem capability case.
+
+## Concerns
+
+No blocking concern. Task 6 client-controller behavior was intentionally left
+untouched.
diff --git a/.superpowers/sdd/ui-task-6-report.md b/.superpowers/sdd/ui-task-6-report.md
new file mode 100644
index 00000000..6a248c44
--- /dev/null
+++ b/.superpowers/sdd/ui-task-6-report.md
@@ -0,0 +1,255 @@
+# Product UI Task 6 report: root-scoped Codex controller
+
+Status: **implemented and verified in the automated test environment**
+
+## RED evidence
+
+Tests were written before `src/codexViewClient.ts` existed. The first bundled
+TypeScript compile failed only on the intended missing production module:
+
+```text
+src/test/codexViewClient.test.ts(5,38): error TS2307: Cannot find module '../codexViewClient'
+```
+
+The initial fixture type error was corrected on the test side and the compile
+was rerun before production implementation, leaving the missing module as the
+sole RED cause. Two later focused RED cycles caught narrower contracts:
+
+- Recommendation scope controls lacked complete tab/tabpanel ARIA linkage.
+- A settings control could post on `click` as well as its intended `change`
+ event, which would duplicate a checkbox update in a browser.
+
+Both focused tests were observed failing for those exact causes before the
+minimal production changes.
+
+## Controller and state contract
+
+- Added `getCodexClientScript(): string`, returning one non-global IIFE. It
+ returns immediately without `[data-codex-root]` and installs only delegated
+ `click`, `input`, `change`, and `keydown` listeners on that root.
+- Every handled action comes from
+ `event.target.closest('[data-codex-action]')`; handled actions prevent default
+ and stop propagation, while unknown, disabled, malformed, or wrong-event
+ actions fail closed.
+- The sole persistent payload is `vscode.getState().codexUi`. Initialization
+ sanitizes, prunes against the current DOM, applies once, then persists while
+ preserving host siblings such as `openDetails`.
+- Restored state includes page/return page, Overview and Recommendation scopes,
+ Explore view/scope, chart metric, bounded search, filters, both sorts,
+ expanded projects, and recursively collapsed task keys. Missing/disabled DOM
+ values and stale safe view keys fall back or are removed.
+- Codex settings accept only the current Codex/shared setting key catalog, the
+ exact expected type/source, bounded or enum/select-valid values, and a strict
+ JSON array of allowlisted reset keys. No evaluation or dynamic function
+ construction is used.
+
+## Production action matrix
+
+| Action | Behavior |
+| --- | --- |
+| `select-page`, `open-settings`, `close-settings` | Page/return-page state, panel visibility, selected/roving tab state |
+| `set-overview-scope`, `set-recommendation-scope` | Truthful independent scope panel selection |
+| `set-model-effort-scope`, `select-behavior-scope` | Shared restored scope for the corresponding nested panels |
+| `set-chart-metric`, `select-chart-metric` | Bars, height, metric class, value, title, y-axis and pressed state |
+| `select-explore-view` | Projects/Sessions/Models & effort tab and panel state |
+| `view-task` | Explore → Sessions, project filter, exact rooted task focus/scroll |
+| `drilldown-date` | Select/focus/scroll the exact row in the clicked Overview scope; no invented hourly data |
+| `filter-sessions`, `remove-filter`, `clear-filters` | Query/role/project/model/effort/verified-period filters, chips, count and flat/tree layout |
+| `toggle-thread-children` | Persisted recursive descendant collapse |
+| `project-sessions` | Persisted project detail expansion |
+| `sort-projects`, `sort-sessions` | Persisted direction and `aria-sort`; project detail and session lineage units travel together |
+| `set-setting`, `reset-settings` | Exact `updateSetting` / `resetAllSettings` host messages after strict validation |
+
+The renderer test derives all production `data-codex-action` values and proves
+that every one has a controller branch. The dormant legacy Behavior component
+action is also accepted without exposing a global handler.
+
+## Renderer and Claude isolation
+
+- Overview now has one trend module containing independent `recent`, `7d`,
+ `30d`, and `all` datasets/panels: the recent task aggregate, verified 7/30-day
+ daily rows, and all-time monthly rows. Inactive panels are hidden.
+- Added safe date/month row attributes, session period membership, rooted task
+ view keys, explicit Explore panel keys, actionable filter chips, controller
+ sort actions, and full Overview/Recommendation tab linkage.
+- Removed the old Codex restore/filter/toggle/show functions, Codex
+ `localStorage` keys, and Codex window globals from `webview.ts`.
+- The Claude document-level sort/chart/bar delegation returns immediately for
+ a Codex-root target and only accepts `.chart-tab[data-metric]`. The delayed
+ all-`.daily-breakdown` rebinding was removed; dynamic Claude drilldowns retain
+ their scoped binder with the same guards.
+
+## Automated verification
+
+Bundled Node was used because `npm` is unavailable and `pnpm` is forbidden.
+
+Focused command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ node_modules/typescript/bin/tsc -p ./ && \
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/codexViewClient.test.js out/test/providerSelection.test.js \
+ out/test/codexView.test.js out/test/codexUiState.test.js
+```
+
+Result: **57 passed, 0 failed**.
+
+Fresh full-suite command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: **386 passed, 0 failed, 1 skipped** out of 387. The skip is the
+pre-existing exact-inode-mtime filesystem capability case.
+
+The executable lightweight DOM fixture runs the emitted IIFE and invokes the
+fixture-mode delegated callbacks. It verifies sibling-preserving/pruned
+restoration, page/scope/metric/filter/collapse/expand/sort/settings/keyboard
+behavior and unknown/malformed fail-closed paths; the Task does not rely only
+on regular expression assertions.
+
+## Review-hardening follow-up
+
+The review follow-up first extended only the lightweight fixture semantics
+needed to express the reported boundaries: enabled/disabled `select` options,
+`valueAsNumber` and `validity`, number constraints, moving `appendChild`, and
+chip removal. The pre-existing five controller tests still passed before the
+new assertions were added, separating fixture errors from production RED
+evidence.
+
+The expanded controller run then produced the intended RED result: **9 passed,
+25 failed** out of 34. The failures independently exercised stale/disabled
+period restoration, invalid number inputs, wrong filter controls/events,
+missing project details, malformed sort inventory, disabled or incomplete
+scope payloads, hidden date panels, and invalid/rooted task navigation. After
+the minimal controller hardening, the controller result was **34 passed, 0
+failed**.
+
+The hardened controller now additionally:
+
+- accepts a restored or changed session-filter value only when the real
+ allowlisted `select` contains an enabled option and at least one session row
+ belongs to it; `disabled` and `aria-disabled="true"` period options fail
+ closed and restore to the empty/tree state;
+- rejects blank/whitespace, browser-invalid, non-finite, input-constraint, and
+ schema-invalid number settings, preferring a finite `valueAsNumber` and
+ otherwise parsing only non-empty text;
+- validates the control kind and exact event type before mutating filter state,
+ and validates project-detail, sort-header/table/row, visible date-panel, and
+ same-project root-task inventories before consuming an action;
+- resolves `view-task` to the actual root row object (including a same-project
+ `data-codex-root-task-view-key` fallback), so child, unknown, and mismatched
+ targets cannot change page or filter state and sorting cannot redirect focus;
+- keeps Recommendation and Models & effort scope changes bound to enabled
+ controls with matching rendered panels.
+
+Fresh review-focused command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ node_modules/typescript/bin/tsc -p ./ && \
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/codexViewClient.test.js out/test/providerSelection.test.js \
+ out/test/codexView.test.js out/test/codexUiState.test.js
+```
+
+Result: **86 passed, 0 failed**.
+
+Fresh review full-suite command:
+
+```sh
+/Users/carl/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node \
+ --test out/test/*.test.js .github/scripts/*.test.mjs
+```
+
+Result: **415 passed, 0 failed, 1 skipped** out of 416. The skip remains the
+pre-existing exact-inode-mtime filesystem capability case. `git diff --check`
+also passed.
+
+This fixture is intentionally not represented as browser-equivalent. It does
+not implement native event bubbling/default control behavior, the browser's
+form constraint-validation engine, layout, rendered focus/scroll behavior, or
+the accessibility tree. Those interactions remain for the Playwright and F5
+Extension Development Host quality gates.
+
+`git diff --check` passed, tracked source/test modes remain `100644`, and the
+forbidden old Codex global/local-storage patterns are absent.
+
+## Final-review correction: rejected filter changes
+
+The preceding statement that a restored **or changed** disabled period would
+return to the empty/tree state was too broad at commit `40a6582`. Restore-time
+pruning did canonicalize the state and DOM, but a live `change` to a disabled,
+`aria-disabled`, or otherwise inventory-invalid option returned before the DOM
+was reapplied. State was not mutated or persisted, yet the browser-mutated
+control value could remain visibly stale.
+
+The final review regression adds a `setState` call counter and asserts the full
+canonical DOM result after the rejected change: empty period selection, tree
+layout, all three fixture rows visible, no active filter chips, and the clear
+button hidden. It also proves there is no additional `setState` call. The RED
+controller run was **31 passed, 3 failed** out of 34, with all three failures
+showing the attempted option value still present. The controller now reapplies
+filters only after a correctly shaped allowlisted `select`/`change` reaches an
+invalid value; it still returns without consuming the event or persisting.
+Wrong events, wrong element types, and unknown filter keys neither consume nor
+resynchronize and retain their attempted fixture value.
+
+After that single production fix, the controller run was **34 passed, 0
+failed**. The fresh compile plus four-file focused command above was **86
+passed, 0 failed**, and the fresh full-suite command was **415 passed, 0
+failed, 1 skipped** out of 416. The skip remains the pre-existing
+exact-inode-mtime filesystem capability case.
+
+## Final-review correction: canonical filters and keyboard ownership
+
+The last review cycle added executable emitted-IIFE regressions before changing
+production. Its RED controller run was **41 passed, 4 failed** out of 45. Three
+failures showed that duplicate filter controls could mutate/persist state or be
+resynchronized; the fourth showed that the generic route still explicitly
+allowed `keydown`. The eight new keyboard behavior/count assertions already
+passed at RED, documenting the existing runtime behavior separately from the
+structural ownership defect.
+
+Filter collection and synchronization now resolve one canonical search input
+and one canonical `select` for each allowlisted filter key. A duplicate control
+is ignored even when its key and value are otherwise legal: it is not consumed,
+resynchronized, persisted, or allowed to mutate state. An invalid value on the
+canonical `select` alone receives the intentional no-persist DOM resync. After
+this identity fix, the intermediate controller run was **44 passed, 1 failed**,
+leaving only the generic-keyboard contract RED.
+
+The generic delegated route now accepts non-filter/settings actions only on
+`click`. The dedicated tab-keydown path owns keyboard activation: Enter and
+Space perform and persist at most once and prevent the native default, while
+ArrowLeft, ArrowRight, Home, and End move roving focus without action or
+persistence. Enter/Space on a non-tab action do nothing; a later click still
+performs and persists exactly once. The GREEN controller run was **45 passed, 0
+failed**.
+
+The fresh four-file focused run was **97 passed, 0 failed**. The fresh full
+suite was **426 passed, 0 failed, 1 skipped** out of 427; the skip remains the
+pre-existing exact-inode-mtime filesystem capability case. `git diff --check`
+also passed.
+
+The lightweight fixture does not generate the browser's native button default
+click after a keydown, so it cannot itself prove absence of that synthesized
+event. It does prove the single-action/persistence counts and the
+`preventDefault` contract. Native Enter/Space event sequencing remains a
+Playwright/browser or F5 Extension Development Host quality-gate boundary.
+
+## ⚠️ Quality-gate concerns
+
+- F5 Extension Development Host smoke testing and the full Playwright
+ interaction/accessibility pass require a GUI-capable VS Code environment and
+ remain for the Product UI quality gate. They were not represented as having
+ run here.
+- Task 7 owns provider-shell visual/accessibility unification. Task 6 adds only
+ the minimum semantic DOM contracts required for truthful controller behavior
+ and does not redesign the Claude or Codex visual system.
+
+---
+🤖 Generated with [OpenAI Codex](https://developers.openai.com/codex/)
diff --git a/.vscodeignore b/.vscodeignore
index 59553de0..ec2a3674 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -4,15 +4,22 @@
.agents/**
.codex/**
.worktrees/**
+.superpowers/**
.github/**
src/**
out/test/**
+tests/**
+playwright.config.mjs
+test-results/**
+playwright-report/**
AGENTS.md
AGENTS.zh-CN.md
CLAUDE.md
CLAUDE.local.md
CONTRIBUTING.md
probe-*.mjs
+issue87-*.cjs
+out/claudeIncrementalPrototype.js
docs/**
.gitignore
diff --git a/AGENTS.md b/AGENTS.md
index 1c85cf63..eab08c5f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,27 +5,43 @@ Simplified-Chinese review copy lives in [AGENTS.zh-CN.md](AGENTS.zh-CN.md).
## Product identity and scope
-- Claude Code Usage is a VS Code extension that reads Claude Code's local JSONL
- usage logs and displays exact token totals, cost estimates, and OAuth quota.
-- The product remains Claude-only in v2.2.1. Codex is a development tool in
- this release, not a completed usage provider or a reason to rename the product.
+- Claude Code Usage is a VS Code extension that reads local Claude Code and
+ Codex usage logs. Claude keeps its exact token totals, cost estimates, and
+ OAuth quota; Codex Beta in v2.3.0 has provider-specific local usage and
+ optimization views plus clearly labelled API-equivalent cost estimates,
+ without pretending those estimates are a bill or subscription charge.
+- Preserve the established product identity and Claude workflows while adding
+ provider-neutral contracts. Claude and Codex dashboard presentation must use
+ the same provider-aware render functions and the same CSS contract.
- Prefer token-attribution accuracy over billing precision. Keep exact totals,
labelled estimates, and point-in-time quota observations as separate concepts.
- Keep the extension local-first, lightweight, and read-mostly. Never modify
- Claude conversation JSONL files. Treat credential handling as security-sensitive
+ Claude or Codex conversation JSONL files. Treat credential handling as security-sensitive
and preserve the existing reviewed behavior.
-- Add no new runtime dependencies in v2.2.1.
+- Add no new runtime dependencies in v2.3.0.
## Architecture boundaries
- `src/extension.ts`: activation, commands, refresh orchestration, watcher,
coalescing, settings changes, and diagnostic output.
-- `src/dataLoader.ts`: JSONL discovery/parsing, deduplication, attribution,
- content analysis, and usage aggregation.
+- `src/dataLoader.ts`: Claude JSONL parsing primitives, validation, attribution,
+ and content-analysis reducers retained for exact compatibility.
+- `src/claudeIncrementalIndex.ts`: the production in-memory per-file Claude
+ usage index, append-tail parser, exact global deduplication, and materialized
+ dashboard aggregates. Runtime refreshes must not fall back to a full-corpus
+ body read or full-record aggregation.
+- `src/providers/providerTypes.ts` and provider adapters: provider-neutral token,
+ coverage, confidence, and limit contracts. Do not erase provider semantics.
+- `src/providers/codex/`: allowed-root discovery, schema guards, exact-request
+ parsing with cumulative high-water fallback, per-file aggregate index, worker
+ protocol, bounded multi-worker cold backfill, and Codex facade.
+- `src/codexView.ts` / `src/codexViewComponents.ts`: Codex copy and default-provider
+ contracts only; they do not own HTML, client code, or styles.
- `src/settings.ts`: the `SETTINGS` catalog and `SettingsStore`; do not scatter
direct configuration reads.
- `src/statusBar.ts`: status-bar token/cost/quota/context presentation.
-- `src/webview.ts`: dashboard HTML and client behavior.
+- `src/webview.ts`: the single provider-aware Claude/Codex dashboard HTML and
+ client behavior. Compare never sums cost or quota across providers.
- `src/i18n.ts`: all user-facing copy for all eight UI locales.
- `src/types.ts`: shared contracts.
- Read `ARCHITECTURE.md` before changing module ownership or the data flow. If
@@ -35,17 +51,65 @@ Simplified-Chinese review copy lives in [AGENTS.zh-CN.md](AGENTS.zh-CN.md).
## Safety and privacy invariants
- Never upload prompt text, response text, raw JSONL lines, absolute paths, raw
- session IDs, credentials, or local usernames. New v2.2.1 performance
+ session IDs, credentials, or local usernames. New v2.3.0 performance
diagnostics must not log them either; do not broaden older diagnostic output
without an explicit privacy review.
+- Codex discovery is allowlisted to `$CODEX_HOME/sessions/**/*.jsonl` and
+ `$CODEX_HOME/archived_sessions/**/*.jsonl` (default `~/.codex`). Never read
+ `auth.json`, SQLite databases, config secrets, keychains, browser state, or
+ unknown files for Codex usage.
+- Additionally, `$CODEX_HOME/session_index.jsonl` may be streamed solely to
+ recover the `id` → `thread_name` mapping used for real thread titles. Absolute
+ paths inside titles are masked, titles stay in memory and are never persisted,
+ symlinks and non-regular files are rejected, and no other field of that file
+ is read.
+- Raw Codex paths/session/parent IDs may exist only in short-lived local worker
+ memory. Persist machine-salted pseudonymous keys and numeric aggregates only.
- Advice and optimizer network calls remain explicit user actions and may send
only the documented digest or text the user pasted.
-- New settings default to existing behavior. Experimental or approximate
- features default off.
+- New settings default to documented, non-surprising behavior. A beta provider
+ may default enabled only when its allowed local directory exists and absence
+ is a no-op; other experimental/approximate features default off unless an
+ approved spec explicitly says otherwise.
- Do not read secret or credential files merely to diagnose a feature. Use
redacted metadata and fixtures.
- Do not hand-edit generated files in `out/`; edit `src/` and compile.
+## Codex Beta data and performance invariants
+
+- Codex processed tokens are `input + output`. Fresh input + output is
+ `max(0, input - cached input) + output`; it is a behavior aid, not a cost or
+ quota equivalent. Cached input is a subset of input and reasoning is a subset
+ of output, so never add either twice.
+- Attribute valid request components from `last_token_usage`; its `total_tokens`
+ field is active-context size, not request usage. Suppress replay only when the
+ full numeric total-plus-last signature matches the same machine-salted
+ rate-limit source or the immediately preceding record. If last usage is
+ absent, fall back to cumulative `total_token_usage` with per-lineage
+ high-water baselines. Counter regressions, unknown parents, and schema drift
+ produce explicit quality flags instead of invented precision.
+- A persisted parser-semantics change must force one bounded automatic rescan.
+ Exclude old aggregates while rebuilding and keep the indexed subtotal and
+ progress visible rather than mixing incompatible totals.
+- Codex rate limits recovered from local logs are `last-observed` only. Drop
+ them after their reset time; do not access credentials or call a network API
+ merely to make them current.
+- 2.4-GB-class history must be indexed in a background worker with a persistent
+ per-file aggregate index. Unchanged warm refresh reads no JSONL body; append
+ refresh reads only the tail. Support progress, cancellation, resume, and
+ single-flight refresh. A one-time incomplete backfill may use an adaptive,
+ bounded local worker pool and coarser durable checkpoints; steady-state work
+ returns to the low-power incremental path.
+- Claude runtime refreshes use the in-memory per-file index in
+ `claudeIncrementalIndex.ts`. An unchanged refresh reads zero JSONL bodies;
+ append/truncate/replace/move/delete work is limited to affected files and
+ affected aggregate groups while preserving the established response-identity
+ and content-analysis semantics. A new Extension Host may still perform one
+ cold in-memory build; this is distinct from rereading the corpus after every
+ watcher event.
+- Codex optimization advice uses structural numeric signals only. Never inspect
+ or persist prompt/response/command bodies or tool arguments.
+
## Development and tests
```bash
@@ -65,6 +129,23 @@ npx @vscode/vsce package
- UI changes also require an F5 Extension Development Host smoke test; release
candidates require an installed-VSIX smoke test on macOS and Linux when available.
+### UI rendering-environment fidelity
+
+For any UI change, first prove that the rendering environment is equivalent to
+a real VS Code webview: every referenced `--vscode-*` variable in the production
+stylesheet must have a real value in the test harness, with a guard test enforcing
+that invariant. Visual snapshots produced before this is true are not acceptance
+evidence.
+Variable values must come from the registered values of VS Code's built-in themes;
+do not mix in another color system.
+
+### Interaction-state acceptance
+
+Webview refresh is a full-page replacement. Every new user-interactive state
+(expand/collapse, sorting, filtering, or selection) must use the existing
+persistence mechanism and include an operation → reload → state-remains test.
+A static assertion of the first render is not acceptance evidence.
+
## Localization and review documents
- Every user-facing string goes through `I18n` with all eight UI locales:
@@ -124,7 +205,11 @@ An unreviewed automated Codex first pass uses exactly:
defaults to DeepSeek and must not be relabelled Codex.
- The wrapper, not model output, owns the trusted footer and emits exactly one.
- Credit Claude Code and OpenAI Codex as development tools in all seven README
- files. Do not put tools in Release Drafter's human contributor list and do
- not invent a `Co-Authored-By` identity.
+ files. Do not put tools in Release Drafter's human contributor list. A commit
+ mainly written by OpenAI Codex must include this exact trailer:
+
+```text
+Co-authored-by: OpenAI Codex <215057067+openai-codex[bot]@users.noreply.github.com>
+```
- Keep controlled comment-only first-pass automation separate from the
privileged maintainer-only mention workflow.
diff --git a/AGENTS.zh-CN.md b/AGENTS.zh-CN.md
index c4bd5214..524e0cab 100644
--- a/AGENTS.zh-CN.md
+++ b/AGENTS.zh-CN.md
@@ -5,23 +5,38 @@
## 产品定位与范围
-- Claude Code Usage 是一个 VS Code 扩展,读取 Claude Code 本地 JSONL 用量
- 日志,展示精确 token 总量、成本估算和 OAuth 配额。
-- v2.2.1 保持 Claude-only。Codex 在这个版本中是开发工具,不是已经完成的
- 用量 provider,也不构成产品改名的理由。
+- Claude Code Usage 是一个读取本地 Claude Code 与 Codex 用量日志的
+ VS Code 扩展。Claude 保留精确 token 总量、成本估算与 OAuth 配额;
+ v2.3.0 直接包含 Codex Beta,提供 provider-specific 的本地用量与优化视图,
+ 以及明确标注的 API 等效成本估算;不得把估算冒充账单或订阅扣费,
+ 也不假定其指标与 Claude 账单等价。
+- 保留既有产品身份与 Claude 工作流,同时增加 provider-neutral contract。
+ Claude 与 Codex 仪表盘必须复用同一组 provider-aware render function 和同一份 CSS contract。
- 优先保证 token 归因准确,不追求账单级精度。精确总量、明确标注的估算、
某一时点的配额观测必须保持为不同概念。
-- 扩展保持 local-first、轻量和 read-mostly。绝不修改 Claude 对话 JSONL;
+- 扩展保持 local-first、轻量和 read-mostly。绝不修改 Claude 或 Codex 对话 JSONL;
credential 处理属于安全敏感范围,只保留已经审阅的既有行为。
-- v2.2.1 不新增 runtime dependency。
+- v2.3.0 不新增 runtime dependency。
## 架构边界
- `src/extension.ts`:激活、命令、刷新调度、watcher、合并刷新、设置变化和诊断输出。
-- `src/dataLoader.ts`:JSONL 发现/解析、去重、归因、内容分析和用量聚合。
+- `src/dataLoader.ts`:Claude JSONL 解析 primitive、校验、归因与内容分析 reducer,
+ 用于保持既有精确语义兼容。
+- `src/claudeIncrementalIndex.ts`:生产环境使用的 Claude 内存 per-file 用量索引、
+ append-tail 解析、精确全局去重与已物化的 dashboard aggregate。运行中刷新不得
+ 回退为全语料 body 重读或全 records 重新聚合。
+- `src/providers/providerTypes.ts` 与 provider adapter:provider-neutral token、
+ coverage、confidence 与 limit contract;不得抹平 provider 语义。
+- `src/providers/codex/`:允许目录发现、schema guard、精确单次请求解析及
+ cumulative high-water 回退、per-file 聚合索引、worker protocol、受限多 worker
+ 冷回填和 Codex facade。
+- `src/codexView.ts` / `src/codexViewComponents.ts`:只保存 Codex 文案与默认
+ provider contract,不负责 HTML、client code 或样式。
- `src/settings.ts`:`SETTINGS` catalog 与 `SettingsStore`;不要散落直接配置读取。
- `src/statusBar.ts`:状态栏 token、成本、配额和 context 展示。
-- `src/webview.ts`:dashboard HTML 与客户端行为。
+- `src/webview.ts`:Claude/Codex 唯一一套 provider-aware dashboard HTML 与客户端行为;
+ Compare 绝不跨 provider 求和成本或配额。
- `src/i18n.ts`:八个 UI locale 的全部用户可见文案。
- `src/types.ts`:共享 contract。
- 改变模块职责或数据流前先读 `ARCHITECTURE.md`;若该变化需要维护者审阅,
@@ -30,14 +45,51 @@
## 安全与隐私不变量
- 绝不上传 prompt、response、原始 JSONL 行、绝对路径、原始 session ID、
- credential 或本地用户名。v2.2.1 新增的性能诊断也不得记录这些内容;未经过
+ credential 或本地用户名。v2.3.0 新增的诊断也不得记录这些内容;未经过
明确隐私审阅,不扩大历史诊断输出范围。
+- Codex 只发现 `$CODEX_HOME/sessions/**/*.jsonl` 与
+ `$CODEX_HOME/archived_sessions/**/*.jsonl`(默认 `~/.codex`)。不得为用量
+ 读取 `auth.json`、SQLite、config secret、keychain、浏览器状态或未知文件。
+- 另允许流式读取 `$CODEX_HOME/session_index.jsonl`,但仅用于恢复真实线程标题所需的
+ `id` → `thread_name` 映射。标题中的绝对路径必须遮蔽,标题仅驻留内存且绝不持久化;
+ 符号链接与非普通文件必须拒绝,并且不读取该文件的任何其他字段。
+- Codex raw path/session/parent ID 只允许存在于本地 worker 的短期内存;
+ 持久化只保留 machine-salted 伪名 key 与数字聚合。
- Advice 和 optimizer 的网络调用必须由用户明确触发,只能发送文档约定的摘要
或用户亲自粘贴的文字。
-- 新设置默认保持既有行为;实验性或近似功能默认关闭。
+- 新设置默认行为必须有明确文档且符合直觉。Beta provider 只能在允许的本地目录
+ 存在时默认启用,目录不存在时必须 no-op;其他实验性/近似功能默认关闭,
+ 除非已批准 spec 明确规定例外。
- 不要仅为了诊断功能而读取 secret 或 credential 文件;使用脱敏 metadata 与 fixture。
- 不要手改 `out/` 生成物;修改 `src/` 后编译。
+## Codex Beta 数据与性能不变量
+
+- Codex processed tokens 是 `input + output`;fresh input + output 是
+ `max(0, input - cached input) + output`,它只是行为优化指标,不是成本或配额等价。
+ Cached input 是 input 子集,reasoning 是 output 子集,绝不重复相加。
+- 有效请求的各 token component 优先取自 `last_token_usage`;其中 `total_tokens`
+ 表示活跃上下文大小,不是该请求的用量。只有完整的 total-plus-last 数字签名与
+ 同一 machine-salted rate-limit 来源或紧邻的上一条记录一致时,才判定为重放。
+ 若缺少 last usage,则回退为按 lineage high-water baseline 解析 cumulative
+ `total_token_usage`。Counter regression、unknown parent 和 schema drift 产生明确
+ quality flag,不伪造精度。
+- 持久化 parser 语义变化必须触发一次有界自动重扫。重建期间排除旧 aggregate,
+ 持续显示已索引小计与进度,不混合两套不兼容的总量。
+- 从 Codex 本地日志恢复的 rate limit 只是 `last-observed`;到达 reset 时间后丢弃,
+ 不为了追求“实时”而读 credential 或发网络请求。
+- 2.4-GB-class 历史必须由 background worker 与持久化 per-file 聚合索引处理。
+ Unchanged warm refresh 不读 JSONL body,append refresh 只读 tail;支持 progress、cancel、resume
+ 与 single-flight。一次性的未完成回填可以使用自适应、受限的本地 worker pool 与
+ 更粗粒度的 durable checkpoint;收敛后必须回到低功耗增量路径。
+- Claude 运行中刷新使用 `claudeIncrementalIndex.ts` 的内存 per-file 索引。
+ Unchanged refresh 的 JSONL body read 必须为 0;append/truncate/replace/move/delete
+ 只处理受影响文件和 aggregate group,同时保持既有 response-identity 去重与
+ content-analysis 语义。新的 Extension Host 仍可执行一次冷内存建索引;这与每次
+ watcher event 都重读全语料是两回事。
+- Codex 优化建议只使用结构化数字信号,不读取或持久化 prompt/response/command body
+ 或 tool arguments。
+
## 开发与测试
```bash
@@ -57,6 +109,19 @@ npx @vscode/vsce package
- UI 变更还要用 F5 Extension Development Host smoke test;候选版本在条件允许时
需要在 macOS 和 Linux 安装 VSIX 做 smoke test。
+### UI 渲染环境保真度
+
+任何 UI 改动,渲染环境必须先证明与真实 VS Code webview 等价:生产样式表中所有
+被引用的 `--vscode-*` 变量必须在测试 harness 中有真实取值,且有守卫测试强制
+这一点。在此之前产生的视觉快照一律不作为验收依据。
+变量的取值必须来自 VS Code 内置主题的注册值,不得混用其他配色体系。
+
+### 交互状态验收
+
+webview 刷新为整页替换。任何新增的用户可交互状态(展开/折叠、排序、筛选、
+选中),必须接入现有持久化机制,并附带一条“操作 → reload → 状态仍在”的测试。
+只验证首次渲染的静态断言不构成验收。
+
## 本地化与审阅文档
- 每个用户可见字符串都通过 `I18n` 覆盖八个 UI locale:`en`、`de-DE`、
@@ -104,6 +169,11 @@ npx @vscode/vsce package
- 归因必须写实际 generator。当前 first-pass runner 默认使用 DeepSeek,不得改标 Codex。
- trusted footer 由 wrapper 而不是模型输出负责,并且只能出现一次。
- 在七份 README 中把 Claude Code 与 OpenAI Codex 记为开发工具;不得把工具放入
- Release Drafter 的人类 contributor 列表,也不得编造 `Co-Authored-By` 身份。
+ Release Drafter 的人类 contributor 列表。主要撰写代码的 OpenAI Codex commit 必须
+ 带以下精确 trailer:
+
+```text
+Co-authored-by: OpenAI Codex <215057067+openai-codex[bot]@users.noreply.github.com>
+```
- comment-only 的受控 first-pass automation 与 maintainer-only 的高权限 mention
workflow 必须保持分离。
diff --git a/ARCHITECTURE-zh-CN.md b/ARCHITECTURE-zh-CN.md
index cf5ad903..d293b4e6 100644
--- a/ARCHITECTURE-zh-CN.md
+++ b/ARCHITECTURE-zh-CN.md
@@ -1,134 +1,218 @@
# 架构说明
-> 本扩展的简明技术地图——它是什么、数据如何流动、以及那些"精确的" token / 成本
-> 数字是怎么算出来的。刻意写得简短:它是给贡献者(或自动化的 issue/PR 助手)
-> 读的参考,让他们**不必**重新通读 `src/`。如果你改变了某个模块的职责或数据
-> 流,请同步更新本文件。英文版见
+> 本文简要说明扩展的 provider 边界、数据流与用量语义。模块职责或
+> provider 行为变化时必须同步更新。英文版见
> [`ARCHITECTURE.md`](ARCHITECTURE.md)。
-## 它是什么(以及不是什么)
+## 产品边界
-**Claude Code Usage** 是一个 VS Code 扩展,通过读取 Claude Code 自己的本地日志,
-在状态栏和仪表盘 webview 中报告 Claude Code 的 token 用量与成本。它刻意保持:
+**Claude Code Usage** 继续保持 local-first、无 runtime dependency 和 read-mostly。
+v2.3.0 保留完整 Claude 体验,并增加 provider-specific 的 Codex Beta 用量与优化视图。
-- **只服务 Claude**——围绕 Claude Code 的日志格式和 Anthropic 的 OAuth 额度构建。
- (为方便 CC-Switch 用户,也给 DeepSeek 等做了定价,但 Claude 是核心。)
-- **轻量**——界面精简、无运行时依赖、次要视图默认折叠。
-- **聚焦 token 归因**——头部的核心数字是**精确的**,直接读自每次请求自带的用量
- 记账,而非估算。
-- 对 `~/.claude` **只读**——绝不写入 Claude Code 的数据。
+- Claude:精确的本地 token bucket、模型成本估算和 Anthropic OAuth 5 小时/每周配额。
+- Codex Beta:本地 processed/fresh/cache/output/reasoning 指标、模型与 effort 拆分、
+ thread 结构、索引 coverage、quality flag 与结构化优化建议。已知模型还会显示明确限定的
+ API 等效成本估算;它绝不是账单或订阅扣费,未知模型保持未定价。
+- Compare:只并列可比指标,绝不把不同 provider 的成本、配额或 token 求和为误导性总量。
-**范围之外**(用这张清单做 issue 初筛):把多供应商/多厂商仪表盘当作一等功能;
-完整的账单/发票对账;写入或驱动 Claude Code;与用量无关的分析。能让 token 洞察、
-归因或建议体验更锋利的需求,才是合适的方向。
+完整账单/发票对账、驱动任一 coding agent 与后台 telemetry 不在范围内。
+Opt-in GitHub 认证和跨设备聚合同步延后到 v2.4.x,届时单独做隐私审阅。
## 模块地图(`src/`)
| 模块 | 职责 |
|---|---|
-| `extension.ts` | 激活、命令注册,以及刷新编排:一个"感知活跃度"、会自我重排的定时器(你活跃时更快、空闲时更缓),带 generation 守卫与合并去重,外加 `pauseDashboardRefresh`。配置经 `SettingsStore` 读取,启动时跑一次设置迁移,监听 workspace 文件夹切换(重取额度),并持有 `runOptimizer`(用量优化器往返)。把 loader + 状态栏 + webview + 额度客户端串起来。 |
-| `dataLoader.ts` | 数据管线。读取 `~/.claude/projects/**/*.jsonl`,解析/校验/去重记录,提取会话标题与用户提示标记,并完成全部聚合(`calculateUsageData` + 今日/本月/会话/项目/分组/分支等口径)。v2.1 还有:`getWorkflowBreakdown`(从 `subagents/` 日志识别多 agent 运行 + 临时批次)、`getUsageAttribution`(用量构成面板)、`getCurrentContextInfo` + `contextWindowFor`(按模型上下文窗口、取主线程记录、`estimated` 标记)、内容/校准分析(`windowDays` 可配)。逻辑量最大的一块。 |
-| `settings.ts` | **(v2.1)** 所有用户设置的单一真源:`SETTINGS` 目录(类型/默认/存储/分组)+ `SettingsStore`。核心三项(`language`、`dataDirectory`、`advice.apiKey`)留 VS Code 配置;其余进 `globalState`,由 dashboard 的 ⚙ 设置标签页编辑。`migrateOnce()` 把 2.1 前 `settings.json` 的值搬进存储。 |
-| `pricing.ts` | 按模型、按 token 的费率表(input / output / 缓存写 / 缓存读分别计价),模型家族推断,以及 `[1m]` 上下文后缀的剥离。`calculateCostBreakdown` 把一条用量记录变成四部分成本。 |
-| `statusBar.ts` | 三个状态栏条目:主条目(今日成本**或** token 量,`statusBarMetric`;三项全隐藏时退化为只剩图标的入口)、额度条目(5h / 每周,可选 `opus:NN%`)、实验性上下文窗口指示器(条形 + 明细 tooltip,窗口为猜测时标 `~`)。 |
-| `webview.ts` | 仪表盘:分页视图(今日、会话、项目、内容、分支、工作流、**⚙ 设置**)、AI 建议 + 用量优化器操作卡、图表与可排序表格。跨重渲染保留优化器状态,并跳过完全相同的重渲染。体量最大的文件——几乎所有 UI 都在这里。 |
-| `claudeApiClient.ts` | Anthropic OAuth 额度:读取 `~/.claude/.credentials.json`,刷新 token(先重读磁盘),从 `api.anthropic.com/api/oauth/usage` 拉取真实用量。走 `httpClient`(fetch → curl);HTTP 429 冷却 60 秒。 |
-| `httpClient.ts` | **(v2.1)** 共享传输层:`requestViaFetch` 与 `requestViaCurl`(curl 的 `cwd` 钉到 home,避免切 workspace 后陈旧 cwd 触发 ENOENT)。curl 是 Anthropic TLS 指纹门 `403 "Request not allowed"` 的兜底。 |
-| `advisor.ts` | AI 建议 + **用量优化器**传输层:`callModel`(Anthropic `/v1/messages` 或 OpenAI chat-completions,带 curl 403 兜底)、`getUsageAdvice`,以及优化器纯函数 `buildOptimizerSystemPrompt` / `parseOptimizerOutput`。只支持 API(订阅分支留休眠)。需手动开启。 |
-| `adviceSummary.ts` | **(v2.1)** 构建发给建议模型的用量摘要——聚合 + 多 agent 运行 + 思考占比 + 归因 + 一段按窗口取的用户自己的 prompt 样本。 |
-| `adviceDemoSample.ts` | 在配置 key 之前展示的静态示例建议(六种语言全覆盖),让功能可被发现。 |
-| `i18n.ts` | 六种语言的字符串表(`en`、`de-DE`、`zh-TW`、`zh-CN`、`ja`、`ko`)+ `SETTINGS_I18N`(⚙ 设置面板每项的标签/帮助)与 `settingText()`。所有面向用户的字符串都经过这里。 |
-| `types.ts` | 共享接口(`ClaudeUsageRecord`、`UsageData`、`SessionUsage`、`SupportedLanguage`、`ExtensionConfig`、`ContextWindowInfo`、额度 + 归因 + 工作流类型……)。 |
-
-## 数据流
-
-```
-~/.claude/projects/<编码后的cwd>/<会话>.jsonl (一个文件 = 一段对话)
- │ 逐行读取,对每行 JSON.parse
- ▼
-校验(是用量记录吗?)─► 去重(messageId+requestId,保留 token 更高的那条)
- │ 给每条记录打上 会话 id + 项目(优先用真实 cwd)
- ▼
-ClaudeUsageRecord[] ──► calculateUsageData() ──► UsageData
- │ (对 4 个 token 桶求和并各自计价) │
- │ ├─► 状态栏(今日 + 额度)
- └─ 会话标题、用户提示标记 └─► webview(各口径明细 + 图表)
+| `extension.ts` | 激活、命令、设置、provider 生命周期、刷新编排、watcher、状态栏/webview 接线和匿名诊断。 |
+| `dataLoader.ts` | 为精确兼容保留的 Claude 解析、校验、归因和内容分析 primitive。 |
+| `claudeIncrementalIndex.ts` | 生产环境 Claude 内存 per-file 索引:append-tail 解析、精确跨文件 response 去重、受影响 group 聚合、内容分析 contribution 和已物化 dashboard row。 |
+| `providers/providerTypes.ts` | Provider-neutral token、event、confidence、outcome、coverage 和 limit contract。 |
+| `providers/claudeProvider.ts` | 薄兼容 adapter,不改变既有 Claude 聚合结果。 |
+| `providers/codex/codexSchema.ts` | 最小安全 JSON guard,不展开或返回 message/command/tool body。 |
+| `providers/codex/codexParser.ts` | Codex 精确单次请求解析及 cumulative high-water 回退、伪名 lineage metadata、结构计数、quality flag 和 last-observed limit。 |
+| `providers/codex/codexManifest.ts` | Codex 允许目录发现、HMAC file key、fingerprint 和 manifest diff。 |
+| `providers/codex/codexIndex.ts` | schema-3 的 per-file 数字聚合与重放证据持久化、有界 cold/tail parse、独立 aggregate/period/current-day coverage 和原子存取。 |
+| `providers/codex/codexIndexWorker.ts` / `codexIndexClient.ts` | 后台协调器、recent-first progress、cancel、resume、checkpoint 持久化和 single-flight client。 |
+| `providers/codex/codexFilePassPool.ts` / `codexFilePassWorker.ts` | 未完成回填期间,用于独立 main、lineage、period、current-day 和 identity per-file pass 的自适应受限本地 pool。 |
+| `providers/codex/codexProvider.ts` | 面向 extension 的 Codex snapshot facade 与 partial/unavailable/error outcome。 |
+| `providers/codex/codexUsage.ts` | Codex 自然日 Today/小时、7 天、30 天、月度、task 与项目 view model 聚合,以及精确模型 API 等效成本。 |
+| `providers/codex/codexInsights.ts` | 确定性的结构用量建议,不读 prompt/body。 |
+| `codexView.ts` / `codexViewComponents.ts` | Codex 本地化文案与默认 provider contract;不负责 HTML renderer、client script 或 CSS。 |
+| `settings.ts` | 权威 `SETTINGS` catalog 和 `SettingsStore`;不得散落直接读取。 |
+| `statusBar.ts` / `codexStatus.ts` | Provider-specific 状态展示和通用 Claude 配额格式化。 |
+| `webview.ts` | Claude/Codex 唯一一套 provider-aware dashboard shell、共享 render function、共享 client 行为、provider tab 与 Compare 展示。 |
+| `i18n.ts` | 八个 UI locale 的全部用户可见文案。 |
+| `types.ts` | 共享 extension 和 Claude contract。 |
+
+`quotaFormat.ts`、`dateKeys.ts`、`shareCard.ts`、`heatmap.ts`、`conversationLog.ts`、
+`miniMarkdown.ts` 等既有纯模块保持当前职责与测试。
+
+## Provider 数据流
+
+```text
+Claude JSONL
+ ──> manifest metadata
+ ──> 内存 per-file 增量索引
+ ──> 精确全局 response identity + 受影响 aggregate group
+ ──> 已物化 Claude 状态栏/dashboard input
+ ──> Claude adapter
+
+允许的 Codex JSONL
+ ──> manifest metadata
+ ──> background worker
+ ──> schema guard + 精确单次请求 parser(lineage high-water 回退)
+ ──> per-file 数字聚合索引 + 定向的当日小时 sidecar
+ ──> CodexProviderSnapshot
+ ──> Codex scope + insight
+ ──> Codex 状态栏 + provider-aware dashboard render input
+
+Claude aggregate + Codex scope ──> 同一套 `webview.ts` dashboard render stack
+Claude aggregate + Codex scope ──> 并列 Compare(不跨 provider 求和)
```
-额度是一条**独立**路径:`claudeApiClient` 调用 OAuth 用量接口,独立于本地日志地
-返回 5 小时 / 7 天 / 7 天 Opus 的用量占比(日志无从得知你套餐的上限,只有
-Anthropic 知道)。
-
-## token 与成本如何计算(精确,而非估算)
-
-JSONL 里每一行助手回复都带一个 `message.usage` 对象——这是 **Anthropic API 自己
-的 token 记账**,与 Anthropic 计费所依据的数字相同。扩展不去估算它们,而是读取、
-校验、去重、求和、计价:
-
-1. **校验**——只保留 `usage.input_tokens` 为数字的记录(真实 API 响应);跳过
- 合成 / 报错 / `` 模型条目。
-2. **去重**——哈希 = `messageId + requestId`;冲突时保留 token 更高的那条(应对
- 某些代理会先记一行占位、再记真实数值的情况)。
-3. **求和**,把四个桶累加进总数:
- - `input_tokens`——新鲜的 prompt,全价
- - `cache_read_input_tokens`——从缓存命中的前缀,约为 input 价的 10%
- - `cache_creation_input_tokens`——**写入**缓存的前缀(即"输入缓存(未命中)"
- 那根条),约为 input 价的 125%;在切换模型或间隔 >5 分钟后会飙升(prompt
- 缓存按模型隔离,TTL 约 5 分钟)
- - `output_tokens`——生成内容,output 价
-4. **计价**——每个桶 × 其按模型的费率(`pricing.ts`);成本 = 求和。
-
-产品中**唯一**的估算,是"内容"标签页里基于字符数的"什么在吃 token"拆解(以及计划
-中的 v2.2 模型匹配 / 缓存浪费功能)——它们始终标注为估算,绝不并入精确总数。
-"消息数"只统计用户手敲的 prompt,靠合成的零 token 标记记录实现,因此永不影响 token
-求和。
-
-## 模型核心功能的日志格式事实(2026-06-13 在磁盘上验证)
-
-JSONL 日志里*有*和*没有*什么——任何要推理运行、模型、effort 的功能的参考。当 Claude
-Code 格式漂移时,用 JSON 键探查重新验证(绝不要用子串 grep——见下方的坑)。
-
-- **运行配置(effort / thinking 预算)不被记录。** 任何用量行上都没有 `effort` /
- `ultracode` / `maxThinking` / 推理预算字段。唯一的 mode 记录是一条 `type:"mode"` 行,
- 带 `permissionMode`(如 `"normal"`)。**我们无法得知一次运行用的是什么 effort 等级。**
- ⇒ 唯一可靠的"动态工作流已启用"信号是 **`subagents/workflows/wf_/` 目录的存在**,
- 而非 effort。普通 Task 工具扇出(无 `wf_` 目录)是"临时批次",不是工作流。
- - **坑:** 子串 grep `effort`/`ultracode` *看起来*命中了——但只在 prompt 正文和工具调用
- 日志里(包括本插件自己的命令一旦被记录)。解析 JSON 键;绝不为配置去 grep。
-- **skill / 插件归因是一等的。** 助手用量行带 `attributionSkill`(如
- `"superpowers:executing-plans"`)和 `attributionPlugin`(如 `"superpowers"`)。它们是
- 权威的——优先用它们而非旧的 `` / `Skill` tool_use 启发式,并用 skill/插件
- 自身行的*精确* `message.usage` 来加权。
-- **为什么原生 Claude 的"工作流"在工作流 tab 里看起来缺失。** Claude 子代理日志*确实*
- 存在(某些 `subagents/` 目录下有 haiku/opus)。但当原生 Claude 运行用 ultracode 时,
- 昂贵的 **Opus/Fable 编排留在主会话日志里**,只有便宜的 **haiku** 子代理(或没有)写
- `agent-*.jsonl`。一个重度 Fable/Opus 的主线程可能**根本没有 `subagents/` 目录**。工作流
- tab 按*子代理文件*分组,所以它显示便宜模型、漏掉主线程成本——这正是 v2.1-PartII 要修的
- (关联运行的编排主会话并显示其成本/模型)。**通则:** *昂贵*模型通常是主线程编排者;
- 子代理文件偏便宜。永远不要只凭子代理文件推断"我主要用哪个模型"。
-- **较新的字段(Claude Code ≥ 2.1)** 值得知道:顶层 `entrypoint`(如 `claude-vscode`)、
- `permissionMode`、`version`、`context_management`;行 `type`:`mode`、`last-prompt`
- (逐字的最后一条用户 prompt)、`queue-operation`、`file-history-snapshot`、`attachment`、
- `system`(hook 摘要);`message.usage` 上:`service_tier`、`iterations`、`speed`、
- `inference_geo`、`cache_creation`(对象)。总量(`input/output/cache_*`)仍然精确。
-
-## 关键不变量(别破坏它们)
-
-- 对 `~/.claude` **只读**(唯一的写是 `claudeApiClient` 刷新 `advice.apiKey` 的 token;绝不动用户日志)。
-- 所有面向用户的字符串都要走 `i18n.ts`、**六种语言齐全**;设置面板的标签/帮助走 `SETTINGS_I18N`(英文回退到目录)。
-- **设置统一走 `SettingsStore`,不要散读。** 只有核心三项(`language`、`dataDirectory`、`advice.apiKey`)声明在 `package.json` / VS Code 配置;其余在 `globalState`,必须经 store 读(`config.get` 找不到)。加一个设置 = `settings.ts` 目录里一条 +(其 `SETTINGS_I18N` 行)。
-- 新设置**默认维持现有行为**(opt-in);实验性/近似功能默认关闭(上下文指示器、用量优化器)。
-- **不新增运行时依赖。** 对外调用(额度、建议/优化器)走 `httpClient` 的 fetch→curl 403 兜底;spawn `curl` 时 `cwd: os.homedir()`。
-- 精确总数与标注过的估算,永不混在一起。
-- 建议/优化器**只**发送用量摘要 / 粘贴的草稿——绝不发送用户的文件。
-
-## 刷新模型
-
-仪表盘 / 状态栏靠 `extension.ts` 里一个感知活跃度的循环保持最新:一个自我重排的
-定时器,你活跃时间隔缩短、空闲时拉长,由 `refreshGen` generation 计数器与合并去重
-守护,避免重叠刷新堆叠。对 `~/.claude/projects` 的文件监听(可关闭)带来约 1.5 秒
-延迟;定时器是兜底。面板打开查看时,`pauseDashboardRefresh` 会冻结更新。
-
-## 发布
-
-TypeScript strict、干净编译(`node ./node_modules/typescript/bin/tsc -p ./`——F5/调试任务用它而非 `npm`,绕开 Windows 的 `npm.ps1` 执行策略拦截)、`node:test` 全绿。仓库在组织 **`ClaudeCodeUsage/ClaudeCodeUsage`**,`main` 有分支保护(PR + `test` 检查)。流程(Release Drafter,v2.0.3 搭好):贡献者提 PR;维护者带版本标签(`patch`/`minor`/`major`,默认 patch)**squash-merge**——这就是每个 PR 的全部操作;草稿 Release 自动攒好分类发布说明;点 **Publish** 打 `v*` 标签,`publish.yml` 盖好 `package.json`、用 `@vscode/vsce` 打包,发到 VS Code Marketplace + Open VSX 并附 `.vsix`。跨 fork PR 的署名由 squash-merge 保住;个别手动重落用 `git merge -s ours` 把贡献者提交留作已合并祖先。关 issue 的 PR 用 `Closes #N`。
+任一 provider unavailable 或 partial 时,不清空另一 provider 最近验证的 snapshot。
+Claude-only 保持 v2.2.1 行为;Codex-only 默认显示 Codex;两者都有时 dashboard 默认 Claude。
+Codex 数据源可用性与已索引数据可用性分开判断:只要检测到允许的本地 home,Codex 标签
+就立即出现,首次建立索引期间由页面实时显示精确文件数、百分比与已处理容量;只有两个
+provider 都已有真实数据时才显示「对比」。worker 启动前先装载最近一次原子检查点;全新
+索引生成第一个检查点后也会在 worker 继续运行时立即采用。因此,进度只是完整「已索引小计」
+仪表盘上的状态说明,不会替代卡片和表格。worker 进度最多每 250 ms 触发一次重绘,且只有
+选中 Codex 时才重绘 Webview;refresh 返回后仍会用已验证 snapshot 做最后一次渲染。
+
+Codex 的「今天」指配置时区中的当前自然日,而不是最近任务。汇总来自该日已验证的 period slice;
+精确小时行来自下文所述的独立当日 sidecar。小时、每日与每月主图默认使用 API 等效成本,Token 构成图
+仍独立展示。未知模型计入 Token 分母但保持未定价,因此定价 coverage 始终可见。Claude 与 Codex 的
+时间序列布局使用对齐的响应式宽度,较密集的图表和表格在各自可键盘聚焦的区域内滚动。
+
+## Token 与 limit 语义
+
+Claude record 带 Anthropic 的四个 token bucket。扩展对其校验、去重、求和并按模型计价。
+Claude 成本仍是根据费率表的估算,不是发票。
+
+Codex 使用以下规则:
+
+- processed = `input total + output total`
+- fresh input + output = `max(0, input total - cached input) + output total`
+- cached input 是 input 子集,reasoning output 是 output 子集
+- 两个子集都不再次加入 processed total
+- fresh input + output 是优化行为的辅助指标,不与成本/配额等价
+
+每条有效的 Codex `token_count` 通常都带 `last_token_usage`;其中 input、cached input、
+output 与 reasoning component 是精确的单次请求归因。`last_token_usage.total_tokens` 表示
+活跃上下文大小,不作为该请求用量。只有 total 与 last 两份 snapshot 的完整数字签名与
+同一伪名 rate-limit 来源或紧邻的上一条记录一致时,才判定为重放。这个窄证明不会把另一条
+交错来源在合法 reset 后的记录静默合并掉。
+
+若缺少 `last_token_usage`,则把 cumulative `total_token_usage` 作为回退;它可能包含继承的
+parent baseline,因此该路径按 component 与 lineage 维护 high-water。Unknown parent、counter
+regression 和 schema drift 产生 quality flag,不生成负用量或伪造精度。
+
+本地日志中的 Codex `rate_limits.primary` 只是 last-observed snapshot,到 reset 时间后隐藏。
+v2.3.0 不读 Codex credential,也不发网络请求刷新它。
+
+## 隐私与持久化
+
+Codex 发现仅限:
+
+- `$CODEX_HOME/sessions/**/*.jsonl`
+- `$CODEX_HOME/archived_sessions/**/*.jsonl`
+- 默认 `$CODEX_HOME`:`~/.codex`
+
+绝不读取 `auth.json`、SQLite、config secret、keychain、浏览器状态或未知文件。
+Raw path/session/parent ID 只留在本地 worker 的短期内存。磁盘只持久化 machine-salted 伪名 key
+和按 day/model/effort/session 聚合的数字,绝不存 prompt、response、command、tool arguments、
+raw line 或 raw path。
+
+Machine salt 存在 VS Code `globalState`,不写入索引。Worker progress/result/error 与 diagnostics
+只含匿名计数与时间,不含 path 或 ID。
+
+### Schema 3 索引契约
+
+内部 schema 3 继续使用既有的 `globalStorage` 文件名 `codex-index-v1.json`;文件名是兼容路径,
+不是 JSON schema 版本声明。持久化 DTO 使用明确的 allowlist:只能写入数字 aggregate、enum、
+伪名 key、清洗后的 label,以及仅由数字 token 计数向量生成的不透明指纹。v3 不保存未完成原始行,
+也不保存 carry buffer。旧字段只在明确命名的 schema-1 legacy migration 边界被读取;该迁移会先
+丢弃 carry,之后才保存 v3 索引。schema 1 与 schema 2 索引都会被标记为需要执行有界 lineage 重扫;
+旧总量不会保留后再叠加到重建结果。若 schema 3 容器里的 per-file parser state 仍早于精确单次请求
+语义,也会执行一次 reset,绝不混合不兼容 aggregate。Parser state 只允许持久化有界的 total-plus-last
+数字签名、对应的 machine-salted 伪名 key,以及紧邻的上一条数字签名。
+
+每个物理 rollout 锁定首个可靠的 session 与 tree 身份。随后用有序的数字事件指纹,在已验证父节点
+中定位 child 复制的前缀,同时保留每个独立 sibling 的后缀。多层 fork 与不同 fork epoch 各自只扣除
+一次复制前缀。若声明的 parent 缺失,child 会保守地按全量计入,并在 UI 显示 `missing-parent` 质量警告,
+不会静默扣除。计数器回退仍按精确 last usage 计入并标为 partial;cumulative 回退路径使用按 component
+的 high-water containment,不产生负 delta,也不会重复计入 reset gap。同一伪名 session 的
+active/archive 副本若存在已验证的有序重叠,该段也只计一次;若身份元数据
+互相冲突,identity coverage 仍保持 incomplete。
+
+这里有两个相互独立的可信度层。all-time 视图来自 canonical file contribution 的已验证的
+aggregate;按日的期间切片则独立晋升,因此 partial migration 不能覆盖、放大或替代 all-time 的
+已验证 aggregate。期间 coverage 以目标时区的 `asOfDay` 为锚点,分别报告 7 天、30 天和
+all-time 的状态。7 天与 30 天只累加自然日内发生的 event;不会因为 Session 的最后活动落在范围内,
+就把该 Session 的整段较早历史吸收进来。
+
+当日小时索引是 schema 3 的增量 sidecar,不是第三份 all-time 事实来源。只有经重复分类选为 canonical,
+且已验证 period slice 已包含 `asOfDay` 的文件才会进入候选集。每个文件的小时晋升状态与处理中 cursor
+都会写入 checkpoint,因此取消后可从已验证 offset 续传。日期或时区变化时会丢弃过期 sidecar,改为
+目标新自然日;该路径既不使主 aggregate 失效,也不会触发全历史重建。
+
+Identity 同样是一份 coverage 契约。Git 的 SCP 形式 SSH URL 与 HTTPS URL 在 host/path 一致时
+会规范化为同一个 repository identity。Root title 采用可信的最新 `updated_at` title;subagent
+保留其报告的 nickname 及 parent title;project 优先显示 canonical repository name,才回退到目录名;
+最近任务排序使用完整 lineage 上观察到的最大活动时间。只有 active/archive 的严格精确副本——两侧
+都已验证且安全 signature 完全一致——才去重;任何其他重复 Session 都标为歧义,并使 identity
+coverage 保持 incomplete,而不是猜测。
+这种稳定歧义属于数据质量状态,不是未完成 I/O:base 与必要 period coverage 完成后,
+dashboard 不再因此一直标为「仍在索引」。
+
+五个结构调用代理量是 `patchCalls`、`toolCalls`、`postPatchToolCalls`、`compactCount` 与
+`taskCompleteCount`。它们只描述观察到的结构 envelope,不是文件、命令或审阅次数;不会产生美元成本,
+也绝不由 prompt、response、command body 或 tool argument 内容推导。
+
+## 刷新与规模
+
+Claude polling 始终遵守 `refreshInterval`,file watcher 使用配置的 quiet debounce。
+生产 Claude 路径维护内存 per-file 索引:unchanged refresh 的 JSONL body read 为 0,
+append 只读已验证 tail,truncate/replace/move/delete 只重建受影响文件和 aggregate group。
+内容分析 contribution 与既有跨文件 response-identity 规则通过同一原子路径更新。新的
+Extension Host 会执行一次冷内存建索引;watcher 驱动的刷新不会重读、重聚合整个语料。
+Codex 使用独立 quiet debounce(默认 30 秒,可选 Off/10/30/60/120/300)。
+
+Codex 按多 GiB 本地历史设计:
+
+- 发现与解析在 Extension Host 之外的 worker 中执行;
+- recent-first 索引,支持 progress 与 cancel;未完成回填最多使用可用逻辑 CPU 的一半,
+ 并把本地 file-pass worker 上限设为 6;索引完成后回到单一低功耗协调路径;
+- unchanged warm refresh 不读 JSONL body;
+- 首次非空索引或尚未完成的旧索引迁移会获得一次受限的 16,384 次文件遍历 / 64 GiB
+ 流式上限;这不是预先分配的内存,并保留取消与原子续传 checkpoint。收敛后,自动任务使用
+ 64 次文件遍历 / 128 MiB,始终可见的手动「刷新」使用 512 次文件遍历 / 2 GiB。
+ 安全下限为 1 MiB + 1 byte,读取 chunk 为 1 MiB,单条 Codex JSONL line 上限为 1 MiB;
+- 实时 progress 保持高频,但大型持久 snapshot 最多约每 10 秒、2 GiB 或 256 个完成的
+ file pass 写入一次,并在最终阶段边界保存。这样既限制崩溃后的重做量,也避免反复写入
+ 数十 MB snapshot 主导高速回填耗时;
+- 只有可能改变 lineage 的阶段才重新 reconcile;period 和稳定阶段复用已验证关系,
+ 不再反复扫描完整索引;
+- 当日小时任务仅处理 period slice 已证明包含 `asOfDay` 的 canonical 文件;它独立 checkpoint/续传,
+ 不会重置主索引;
+- append refresh 只读新 tail;未完成行只留在 scanner 的短期内存,从 safe cursor 重试,绝不写入 v3;
+- truncate/replacement 只重解析受影响文件;
+- cancel checkpoint 会原子保存 per-file contribution 与 migration progress,下一轮从已验证 cursor resume;
+- 并发 refresh 共享同一 worker run。
+
+每周 API 等效价值历史只从已经聚合的 Token 用量生成。有真实每周重置观测时,七天窗口按该重置
+对齐;没有时,仅已用历史按 UTC 周一至周一的自然周分组。Token 日志能够证明已用价值,但不能证明
+历史订阅总额度,因此总额度与未用额度仅在存在真实额度用量比例观测时生成。Codex 的仅已用历史可能
+合并同一 home 中的多个登录,而额度推算行仍绑定其实际观测的重置序列。
+
+v2.3.0 不推断 20、100 或 200 美元的订阅档位。本地 Codex 日志没有可靠的账号与套餐身份,
+后续比较必须采用用户明确选择的 opt-in 账号映射,不能猜测后绑定价格。
+
+## 发布不变量
+
+- 根据变更风险执行 strict TypeScript、red-green TDD、完整 `node:test`、F5 smoke test
+ 和安装 VSIX smoke test。
+- 用户可见字符串覆盖 `en`、`de-DE`、`zh-TW`、`zh-CN`、`ja`、`ko`、`pt-BR`、`id`;
+ 七份 README 同步。
+- 不手工修改 `package.json` 版本。发布已审阅的 Release Drafter draft 后才创建 tag,
+ publish workflow 再从 tag 写入包版本。
+- 通过合并贡献者原 PR,或经授权先修改该 PR 分支再合并,保留贡献归属。
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 7e5ef593..baf4852b 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,199 +1,281 @@
# Architecture
-> A concise technical map of this extension — what it is, how data flows, and
-> how the exact token/cost numbers are produced. Kept deliberately short: it is
-> the reference a contributor (or an automated issue/PR helper) reads *instead
-> of* re-reading `src/`. If you change a module's role or the data flow, update
-> this file. A Simplified-Chinese version lives in
+> A concise map of the extension's provider boundaries, data flow, and usage
+> semantics. Update it whenever module ownership or provider behavior changes.
+> A faithful Simplified-Chinese companion lives in
> [`ARCHITECTURE-zh-CN.md`](ARCHITECTURE-zh-CN.md).
-## What it is (and isn't)
+## Product boundary
-**Claude Code Usage** is a VS Code extension that reports Claude Code token
-usage and cost — in the status bar and a dashboard webview — by reading Claude
-Code's own local logs. It is deliberately:
+**Claude Code Usage** remains local-first, dependency-free, and read-mostly.
+v2.3.0 preserves the complete Claude experience and adds Codex Beta as a
+provider-specific usage and optimization view.
-- **Claude-only** — built around Claude Code's log format and Anthropic's OAuth
- quota. (DeepSeek etc. are priced as a courtesy for CC-Switch users, but Claude
- is the focus.)
-- **Lightweight** — minimal UI, no runtime dependencies, secondary views
- collapsed by default.
-- **Token-attribution focused** — the headline numbers are *exact*, read from
- each request's own usage accounting, not estimated.
-- **Read-only** over `~/.claude` — it never writes to Claude Code's data.
+- Claude: exact local token buckets, model pricing estimates, and Anthropic
+ OAuth 5-hour/weekly quota.
+- Codex Beta: local processed/fresh/cache/output/reasoning metrics, model and
+ effort breakdowns, thread structure, index coverage, quality flags, and
+ structural optimization guidance. Known models also receive a clearly
+ qualified API-equivalent cost estimate; it is never a bill or subscription
+ charge, and unknown models remain unpriced.
+- Compare: side-by-side compatible metrics only. It never sums provider cost,
+ quota, or tokens into a misleading combined total.
-**Out of scope** (use this list to triage requests): multi-provider/vendor
-dashboards as a first-class feature; full billing/invoice reconciliation;
-writing to or driving Claude Code; non-usage analytics. Requests that sharpen
-token insight, attribution, or the advice experience are the right fit.
+Full invoice reconciliation, driving either coding agent, and background
+telemetry are out of scope. Opt-in GitHub authentication and cross-device
+aggregate sync are deferred to v2.4.x after a separate privacy review.
## Module map (`src/`)
| Module | Role |
|---|---|
-| `extension.ts` | Activation, command registration, and the refresh orchestration: an activity-aware, self-rescheduling timer (faster while you're active, calmer when idle) with a generation guard and coalescing, plus `pauseDashboardRefresh`. Reads config through `SettingsStore`, runs the settings migration once, listens for workspace-folder changes (re-fetches quota), and owns `runOptimizer` (the Usage Optimizer round-trip). Wires loader + status bar + webview + quota client together. |
-| `dataLoader.ts` | The data pipeline. Reads `~/.claude/projects/**/*.jsonl`, parses/validates/dedups records, harvests session titles and user-prompt markers, and aggregates everything (`calculateUsageData` + today/month/session/project/group/branch breakdowns). Also v2.1: `getWorkflowBreakdown` (multi-agent runs from `subagents/` logs + ad-hoc batches), `getUsageAttribution` (the "what's contributing" panel), `getCurrentContextInfo` + `contextWindowFor` (per-model context window, main-thread record, `estimated` flag), and the content/calibration analysis (`windowDays` configurable). The largest piece of logic. |
-| `settings.ts` | **(v2.1)** Single source of truth for every user setting: the `SETTINGS` catalog (type/default/storage/group) + `SettingsStore`. A small core (`language`, `dataDirectory`, `advice.apiKey`) lives in VS Code config; the rest in `globalState`, edited from the dashboard's ⚙ Settings tab. `migrateOnce()` copies pre-2.1 `settings.json` values into the store. |
-| `pricing.ts` | Per-model, per-token rate table (separate input / output / cache-write / cache-read rates), model-family inference, and `[1m]`-context-suffix stripping. `calculateCostBreakdown` turns a usage record into a four-part cost. |
-| `statusBar.ts` | Three status-bar items: the primary (today's cost **or** token count, `statusBarMetric`; icon-only entry fallback when all three are hidden), the quota item (5h / weekly, opt-in `opus:NN%`), and the experimental context-window indicator (bar + breakdown tooltip, `~` for guessed windows). |
-| `webview.ts` | The dashboard: tabbed views (today, sessions, projects, content, branches, workflows, **⚙ settings**), the AI-advice + Usage-Optimizer action cards, charts and sortable tables. Holds optimizer state across re-renders and skips identical re-renders. By far the biggest file — almost all UI lives here. |
-| `claudeApiClient.ts` | Anthropic OAuth quota: reads `~/.claude/.credentials.json`, refreshes the token (re-reading disk first), and fetches real utilisation from `api.anthropic.com/api/oauth/usage`. Uses `httpClient` (fetch → curl). 60s cool-down on HTTP 429. |
-| `httpClient.ts` | **(v2.1)** Shared transport: `requestViaFetch` and `requestViaCurl` (curl `cwd` pinned to home so a stale workspace cwd can't ENOENT it). curl is the fallback for Anthropic's TLS-fingerprint `403 "Request not allowed"` gate. |
-| `advisor.ts` | The AI-advice + **Usage Optimizer** transport: `callModel` (Anthropic `/v1/messages` or OpenAI chat-completions, with the curl 403 fallback), `getUsageAdvice`, and the optimizer's pure helpers `buildOptimizerSystemPrompt` / `parseOptimizerOutput`. API-only (a dormant subscription branch remains). Opt-in. |
-| `adviceSummary.ts` | **(v2.1)** Builds the usage digest sent to the advice model — aggregates + multi-agent runs + thinking share + attribution + a windowed sample of the user's own prompts. |
-| `adviceDemoSample.ts` | Static sample advice (all seven languages) shown before a key is configured, so the feature is discoverable. |
-| `i18n.ts` | The string table for seven languages (`en`, `de-DE`, `zh-TW`, `zh-CN`, `ja`, `ko`, `pt-BR`) + `SETTINGS_I18N` (per-setting label/help for the ⚙ Settings panel) and `settingText()`. Every user-facing string goes through here. |
-| `types.ts` | Shared interfaces (`ClaudeUsageRecord`, `UsageData`, `SessionUsage`, `CostlyMessage`, `SupportedLanguage`, `ExtensionConfig`, `ContextWindowInfo`, quota + attribution + workflow types, …). |
-
-### V2.2 additions (pure, unit-tested modules)
-| Module | Role |
-|---|---|
-| `shareCard.ts` | Pure share-card logic: `buildShareCardData` (privacy redaction — the type carries no prompt/path/id field), `ShareSections`/`DEFAULT_SECTIONS`, `selectShareBadge`, `prettyModelName`, `rangeLabel`, `shareCardFilename`. |
-| `shareCardSvg.ts` | Self-contained SVG renderer for the share card (no DOM/fonts/network). `SHARE_CARD_THEMES` (claudeCream / claudeClassic / auroraDark) + `resolveShareCardTheme`; `BADGE_COPY`. Landscape 1200×680. |
-| `heatmap.ts` / `heatmapSvg.ts` | GitHub-contribution-style token heatmap logic + SVG (`buildContributionGrid`, `renderHeatmapSvg`). Exported/published to a GitHub profile repo (`extension.publishHeatmapToGitHub`, built-in GitHub auth + Contents API). |
-| `dateKeys.ts` | Timezone-consistent day/month bucketing (`dayKeyInZone`/`monthKeyInZone`) so dashboard buckets match the configured zone. |
-| `promptDedup.ts` | Dedups api_error retry re-logs so message counts aren't inflated. |
-| `quotaFormat.ts` | Pure status-bar quota text formatter (kept clean; reset detail lives in the tooltip). |
-| `dataContribution.ts` | Opt-in, OFF-by-default SCAFFOLD for future authorized cross-user signals (e.g. cache-TTL by tier). No endpoint / no-op; the Observation type has no identifying field. |
-| `sessionResume.ts` | **(v2.2)** Pure guards for the Sessions resume/copy actions: `isValidSessionId`, `isUsableCwd`, `buildResumeCommand`, `isUnderDir` (shell-injection-safe id/cwd validation). |
-| `conversationLog.ts` | **(v2.2)** Pure parser for the read-only conversation viewer: a session `.jsonl` → ordered display turns (prompt / text / thinking / tool_use / tool_result). Skips meta/sidechain, dedups by uuid, caps by ROUNDS (`maxRounds`) so prompts aren't starved, truncates per turn. Unit-tested. |
-| `conversationViewerHtml.ts` | **(v2.2)** Renders a `ParsedConversation` to a full read-only webview page (no scripts): prompts as the star, Markdown-rendered answers, thinking/tools behind pure-CSS toggles, a prompt jump-nav. |
-| `miniMarkdown.ts` | **(v2.2)** Dependency-free Markdown→HTML (headings, lists, GFM tables, code, links, blockquotes) used by the conversation viewer; escapes before formatting, scheme-checks links. Unit-tested. |
-
-New data-layer methods on `dataLoader.ts`: `getCostliestMessages` (top-N single
-turns + prompt + skill + cost split + gap + prev-model, for the Content tab's
-"costliest messages"), `estimateCacheTtl` (hit-rate-by-gap-bin → warm-window
-estimate; measured ~60 min), `buildShareInput` (range today/week/last30/month/
-year/`month:YYYY-MM` + scope all/project/session). Efficiency insights
-(cost/tokens per message, realised cache savings, "Cache warmth"), thinking-share
-"hidden" handling (Fable 5 / Opus omit CoT text), and timezone = full UTC-offset
-coverage are all V2.2. The opt-in **Experimental insights** (`showInsights`) draw
-on `estimateCacheChurnCost`, `cacheStatsByModel` (per-model warm window),
-`sessionHealth` (big one-shot turns), `activeHours`, and `skillRoi`; plus
-`activeDurationBySession` for the Sessions "Active" column and `modelRightsizing`
-(computed but not shown — reserved for an AI-judged v2). All weekly-cached in the
-webview. CI: `@ccu-bot` first-pass automation (`claude.yml`,
-`issue-first-pass.yml`, `pr-first-pass.yml`) — INERT unless `AUTOMATION_ENABLED`;
-official or third-party (`ANTHROPIC_BASE_URL`) key; reads this file.
-
-## Data flow
+| `extension.ts` | Activation, commands, settings, provider lifecycle, refresh orchestration, watchers, status/webview wiring, and anonymous diagnostics. |
+| `dataLoader.ts` | Claude parsing, validation, attribution, and content-analysis primitives retained for exact compatibility. |
+| `claudeIncrementalIndex.ts` | Production in-memory per-file Claude index: append-tail parsing, exact cross-file response deduplication, affected-group aggregation, content-analysis contributions, and materialized dashboard rows. |
+| `providers/providerTypes.ts` | Provider-neutral token, event, confidence, outcome, coverage, and limit contracts. |
+| `providers/claudeProvider.ts` | Thin compatibility adapter that exposes existing Claude aggregates without changing their results. |
+| `providers/codex/codexSchema.ts` | Minimal safe JSON guards; never flattens or returns message/command/tool bodies. |
+| `providers/codex/codexParser.ts` | Codex exact-request parsing with cumulative high-water fallback, pseudonymous lineage metadata, structural counters, quality flags, and last-observed limits. |
+| `providers/codex/codexManifest.ts` | Allowlisted Codex directory discovery, HMAC file keys, fingerprints, and manifest diffing. |
+| `providers/codex/codexIndex.ts` | Schema-3 persistent per-file numeric aggregates and replay evidence, bounded cold/tail parsing, independent aggregate/period/current-day coverage, and atomic save/load. |
+| `providers/codex/codexIndexWorker.ts` / `codexIndexClient.ts` | Background coordinator, recent-first progress, cancellation, resume, checkpoint persistence, and single-flight client. |
+| `providers/codex/codexFilePassPool.ts` / `codexFilePassWorker.ts` | Adaptive bounded local pool for independent per-file main, lineage, period, current-day, and identity passes during incomplete backfills. |
+| `providers/codex/codexProvider.ts` | Extension-facing Codex snapshot facade and partial/unavailable/error outcomes. |
+| `providers/codex/codexUsage.ts` | Codex calendar-Today/hourly, 7-day, 30-day, monthly, task, and project view-model aggregation with exact-model API-equivalent cost. |
+| `providers/codex/codexInsights.ts` | Deterministic structural usage guidance; no prompt/body inspection. |
+| `codexView.ts` / `codexViewComponents.ts` | Codex localized-copy and default-provider contracts; no HTML renderer, client script, or CSS ownership. |
+| `settings.ts` | Canonical `SETTINGS` catalog and `SettingsStore`; do not scatter direct reads. |
+| `statusBar.ts` / `codexStatus.ts` | Provider-specific status presentation and generic Claude quota formatting. |
+| `webview.ts` | Single provider-aware Claude/Codex dashboard shell, shared render functions, shared client behavior, provider tabs, and Compare presentation. |
+| `i18n.ts` | All user-facing copy for all eight UI locales. |
+| `types.ts` | Shared extension and Claude contracts. |
+Existing pure modules such as `quotaFormat.ts`, `dateKeys.ts`, `shareCard.ts`,
+`heatmap.ts`, `conversationLog.ts`, and `miniMarkdown.ts` keep their current
+ownership and tests.
+
+## Provider data flow
+
+```text
+Claude JSONL
+ ──> manifest metadata
+ ──> in-memory per-file incremental index
+ ──> exact global response identity + affected aggregate groups
+ ──> materialized Claude status/dashboard inputs
+ ──> Claude adapter
+
+allowlisted Codex JSONL
+ ──> manifest metadata
+ ──> background worker
+ ──> schema guard + exact-request parser with lineage high-water fallback
+ ──> per-file numeric aggregate index + targeted current-day hourly sidecar
+ ──> CodexProviderSnapshot
+ ──> Codex scopes + insights
+ ──> Codex status + provider-aware dashboard render inputs
+
+Claude aggregates + Codex scopes ──> one `webview.ts` dashboard render stack
+Claude aggregates + Codex scopes ──> side-by-side Compare (no cross-provider totals)
```
-~/.claude/projects//.jsonl (one file = one conversation)
- │ read line-by-line, JSON.parse each line
- ▼
-validate (is it a usage record?) ─► dedup (messageId+requestId, keep higher tokens)
- │ tag each record with session id + project (real cwd preferred)
- ▼
-ClaudeUsageRecord[] ──► calculateUsageData() ──► UsageData
- │ (sum the 4 token buckets, price each) │
- │ ├─► status bar (today + quota)
- └─ session titles, user-prompt markers └─► webview (breakdowns + charts)
-```
-Quota is a **separate** path: `claudeApiClient` calls the OAuth usage endpoint
-and returns five-hour / seven-day / seven-day-opus utilisation independently of
-the local logs (the logs cannot know your plan's limits; only Anthropic does).
-
-## How tokens & cost are computed (exact, not estimated)
-
-Each assistant-response line in the JSONL carries a `message.usage` object — the
-**Anthropic API's own token accounting**, the same counts Anthropic bills on.
-The extension does not estimate these; it reads, validates, dedups, sums, and
-prices them:
-
-1. **Validate** — keep only records whose `usage.input_tokens` is a number (real
- API responses); skip synthetic/error/`` model entries.
-2. **Dedup** — hash = `messageId + requestId`; on collision keep the
- higher-token copy (handles proxies that log a placeholder line first, then
- the real values).
-3. **Sum** the four buckets into the totals:
- - `input_tokens` — fresh prompt, full price
- - `cache_read_input_tokens` — prefix served from cache, ~10% of input price
- - `cache_creation_input_tokens` — prefix *written* to cache (the
- "Input Cache (Miss)" bar), ~125% of input price; spikes on a model switch
- or after an idle gap past the cache TTL (the prompt cache is per-model; the
- TTL is platform-side — MEASURED at ~60 min in practice, not the assumed
- 5 min — and `estimateCacheTtl` infers it from the user's own turns)
- - `output_tokens` — generated, output price
-4. **Price** — each bucket × its per-model rate (`pricing.ts`); cost = the sum.
-
-The **only** estimates in the product are the Content tab's character-based
-"what's consuming tokens" breakdown (and the planned v2.2 model-fit /
-cache-waste features) — always labelled as estimates and never folded into the
-exact totals. "Messages" counts user-typed prompts only, via synthetic
-zero-token marker records, so it never affects token sums.
-
-## Log-format facts for model-core features (verified on disk 2026-06-13)
-
-What the JSONL logs *do* and *don't* contain — the reference for any feature
-that reasons about runs, models, or effort. Re-verify with a JSON-key probe
-(never a substring grep — see the trap below) when Claude Code's format drifts.
-
-- **Run config (effort / thinking budget) is NOT logged.** There is no
- `effort` / `ultracode` / `maxThinking` / reasoning-budget field on any usage
- line. The only mode record is a `type:"mode"` line carrying `permissionMode`
- (e.g. `"normal"`). **We cannot tell what effort level a run used.** ⇒ the only
- reliable "dynamic-workflow engaged" signal is the **presence of a
- `subagents/workflows/wf_/` directory**, not effort. A plain Task-tool
- fan-out (no `wf_` dir) is an "ad-hoc batch", not a workflow.
- - **Trap:** a substring grep for `effort`/`ultracode` *appears* to hit — but
- only inside prompt text and tool-call logs (including this extension's own
- commands once they're logged). Parse JSON keys; never grep for config.
-- **Skill / plugin attribution IS first-class.** Assistant usage lines carry
- `attributionSkill` (e.g. `"superpowers:executing-plans"`) and
- `attributionPlugin` (e.g. `"superpowers"`). These are authoritative — prefer
- them over the older `` / `Skill` tool_use heuristic, and
- cost-weight a skill/plugin by the *exact* `message.usage` of its own lines.
-- **Why native-Claude "workflows" look missing from the Workflows tab.** Claude
- sub-agent logs *do* exist (haiku/opus appear under some `subagents/` dirs).
- But when a native-Claude run uses ultracode, the expensive **Opus/Fable
- orchestration stays in the main session log**, and only cheap **haiku**
- sub-agents (or none) write `agent-*.jsonl`. A heavy Fable/Opus main thread can
- produce **no `subagents/` dir at all**. The Workflows tab groups *sub-agent
- files*, so it shows the cheap models and misses the main-thread cost — hence
- the v2.1-PartII fix to associate a run's orchestrating main session and surface
- its cost/models. **General principle:** the *expensive* model is usually the
- main-thread orchestrator; sub-agent files skew cheap. Never infer "which model
- I mainly use" from sub-agent files alone.
-- **Newer fields (Claude Code ≥ 2.1)** worth knowing: top-level `entrypoint`
- (e.g. `claude-vscode`), `permissionMode`, `version`, `context_management`; line
- `type`s `mode`, `last-prompt` (verbatim last user prompt), `queue-operation`,
- `file-history-snapshot`, `attachment`, `system` (hook summaries); and on
- `message.usage`: `service_tier`, `iterations`, `speed`, `inference_geo`,
- `cache_creation` (object). Totals (`input/output/cache_*`) remain exact.
-
-## Key invariants (don't break these)
-
-- **Read-only** over `~/.claude` (the one write is `advice.apiKey` token refresh in `claudeApiClient`; never touch user logs).
-- Every user-facing string goes through `i18n.ts` in **all seven languages**; settings-panel labels/help go through `SETTINGS_I18N` (English fallback from the catalog).
-- **Settings live in `SettingsStore`, not scattered reads.** Only the core trio (`language`, `dataDirectory`, `advice.apiKey`) is declared in `package.json` / VS Code config; everything else is `globalState` and must be read via the store (so `config.get` won't find it). Adding a setting = one catalog entry in `settings.ts` (+ its `SETTINGS_I18N` rows).
-- New settings **default to existing behaviour** (opt-in); experimental/approximate features default OFF (context indicator, Usage Optimizer).
-- **No new runtime dependencies.** External calls (quota, advice/optimizer) go through `httpClient` with the fetch→curl 403 fallback; spawn `curl` with `cwd: os.homedir()`.
-- Exact totals and labelled estimates never mix.
-- Advice/Optimizer send **only** the usage digest / the pasted draft — never the user's files.
-
-## Refresh model
-
-The dashboard/status bar stay current via an activity-aware loop in
-`extension.ts`: a self-rescheduling timer whose interval shortens when you're
-active and lengthens when idle, guarded by a `refreshGen` generation counter and
-coalescing so overlapping refreshes can't stack. File-watching of
-`~/.claude/projects` (opt-out) gives ~1.5s latency; an interval is the fallback.
-`pauseDashboardRefresh` freezes updates while the panel is open for inspection.
-
-## Release
-
-TypeScript strict, clean compile (`node ./node_modules/typescript/bin/tsc -p ./`
-— the F5/debug task uses this, not `npm`, to dodge the Windows `npm.ps1`
-execution-policy block), green `node:test`. The repo lives at the org
-**`ClaudeCodeUsage/ClaudeCodeUsage`**; `main` is branch-protected (PR + `test`
-check). Flow (Release Drafter, set up in v2.0.3): contributors open PRs; the
-maintainer **squash-merges** with a version label (`patch`/`minor`/`major`,
-default patch) — that's the whole per-PR action; a **draft GitHub Release**
-auto-accumulates categorised notes; clicking **Publish** tags `v*` and
-`publish.yml` stamps `package.json`, packages with `@vscode/vsce`, and ships to
-the VS Code Marketplace + Open VSX with the `.vsix` attached. A cross-fork PR's
-attribution is preserved by squash-merge; the rare manual re-apply uses
-`git merge -s ours` to keep the contributor's commits as merged ancestors.
-Issue-closing PRs use `Closes #N`.
+One provider may be unavailable or partial without clearing the other
+provider's last verified snapshot. Claude-only remains the v2.2.1 behavior;
+Codex-only defaults to Codex; when both exist, the dashboard defaults to Claude.
+Codex source availability is detected separately from indexed-data availability:
+the Codex tab appears as soon as an allowed local home exists, an in-page state
+covers the initial index with exact file, percentage, and byte counts, and
+Compare remains hidden until both providers have real data. Before the worker
+starts, the extension hydrates the last atomic checkpoint; a brand-new index
+adopts its first checkpoint while the worker continues. Thus progress is an
+annotation on the full indexed-subtotal dashboard rather than a replacement for
+its cards and tables. Worker progress is coalesced to at most one Webview render
+every 250 ms and repaints only while Codex is selected; the verified snapshot
+still gets a final render when the refresh returns.
+
+Codex Today is the current civil day in the configured timezone, not the most
+recent task. Its summary uses that day's verified period slice; exact hourly
+rows come from the independent current-day sidecar described below. Hourly,
+daily, and monthly primary charts use API-equivalent cost by default, while the
+token-composition chart remains a separate view. Unknown models contribute to
+the token denominator but remain unpriced, so pricing coverage stays visible.
+Claude and Codex time-series layouts share aligned responsive widths and keep
+dense chart/table content inside local keyboard-focusable scrollers.
+
+## Token and limit semantics
+
+Claude records carry Anthropic's four token buckets. The extension validates,
+deduplicates, sums, and prices them by model. Claude cost remains an estimate
+from the configured rate table; it is not an invoice.
+
+Codex uses these rules:
+
+- processed = `input total + output total`
+- fresh input + output = `max(0, input total - cached input) + output total`
+- cached input is a subset of input; reasoning output is a subset of output
+- neither subset is added again to processed totals
+- fresh input + output is an optimization aid, not a cost/quota equivalence
+
+Each valid Codex `token_count` normally carries `last_token_usage`; its input,
+cached-input, output, and reasoning components are the exact request-level
+attribution. `last_token_usage.total_tokens` is the active context size and is
+not attributed as request usage. A full numeric signature of both total and
+last snapshots suppresses replay only when it matches the same pseudonymous
+rate-limit source or the immediately preceding record. This narrow proof avoids
+silently merging a legitimate reset from another interleaved source.
+
+If `last_token_usage` is absent, `total_token_usage` remains a cumulative
+fallback and may include an inherited parent baseline. That path uses
+per-component, per-lineage high-water marks. Unknown parents, regressions, and
+schema drift produce quality flags rather than negative or fabricated usage.
+
+Codex `rate_limits.primary` found in local logs is a last-observed snapshot only.
+It is hidden once its reset time passes. v2.3.0 does not read Codex credentials
+or make a network call to refresh it.
+
+## Privacy and persistence
+
+Codex discovery is restricted to:
+
+- `$CODEX_HOME/sessions/**/*.jsonl`
+- `$CODEX_HOME/archived_sessions/**/*.jsonl`
+- default `$CODEX_HOME`: `~/.codex`
+
+It never reads `auth.json`, SQLite databases, config secrets, keychains, browser
+state, or unknown files. Raw paths/session/parent IDs stay in short-lived local
+worker memory. Disk persistence contains machine-salted pseudonymous keys and
+numeric per-day/model/effort/session aggregates only—never prompt, response,
+command, tool-argument, raw-line, or raw-path content.
+
+The machine salt lives in VS Code `globalState`, not in the index file. Worker
+progress/results/errors and diagnostics contain anonymous counts and timings,
+not paths or identifiers.
+
+### Schema 3 index contract
+
+Schema 3 deliberately keeps the established `globalStorage` filename
+`codex-index-v1.json`; the filename is a compatibility path, not a statement
+about the JSON schema. Its persisted DTO is an explicit allowlist of numeric
+aggregates, enum values, pseudonymous keys, cleaned labels, and opaque
+fingerprints derived only from numeric token-counter vectors. A v3 file never
+stores a raw incomplete line or a carry buffer. The only reader for those old
+fields is the explicitly named legacy schema-1 migration boundary; it discards
+the carry before the v3 index is saved. Schema-1 and schema-2 indexes are marked
+for a bounded lineage rescan; their prior totals are not retained and added to
+the rebuilt result. A schema-3 container whose per-file parser state predates
+exact-request semantics is also reset once, so incompatible aggregates are
+never mixed. The parser state may persist only bounded numeric total-plus-last
+signatures keyed by machine-salted pseudonyms, plus the immediately preceding
+numeric signature.
+
+Each physical rollout locks its first reliable session and tree identity. An
+ordered numeric-event fingerprint trace then finds the copied prefix of a child
+inside its verified parent while retaining every independent sibling suffix.
+Nested forks and separate fork epochs apply their own prefix once. If the
+reported parent is absent, the child stays conservatively counted in full and a
+visible `missing-parent` quality warning replaces silent subtraction. Counter
+regressions still emit exact last usage with partial confidence; the cumulative
+fallback uses component high-water containment and never creates negative
+deltas or counts a reset gap again. A verified ordered overlap for the same
+pseudonymous session across active/archive copies is likewise counted once,
+while conflicting identity metadata still keeps identity coverage incomplete.
+
+There are two separate truth layers. The all-time view is built from the
+verified aggregate of canonical file contributions. Time-bucketed period slices
+are promoted independently, so a partial migration cannot overwrite, inflate,
+or stand in for that all-time verified aggregate. Period coverage is anchored by
+the target-zone `asOfDay` and reports separate 7-day, 30-day, and all-time
+states. The 7/30-day views sum events in their natural calendar days; they do
+not pull an entire older session into a range merely because the session's last
+activity falls inside it.
+
+The current-day hourly index is an additive schema-3 sidecar, not a third source
+of all-time truth. A file becomes eligible only after duplicate classification
+selects it as canonical and its verified period slice already contains
+`asOfDay`. Per-file hourly promotion and in-progress cursors are checkpointed,
+so cancellation resumes from the verified offset. A day or timezone change
+discards the stale sidecar and targets the new civil day. This path neither
+invalidates the primary aggregate nor triggers a full-history reindex.
+
+Identity is also a coverage contract. Git SCP-style SSH and HTTPS repository
+URLs are canonicalized to the same repository identity where their host/path
+matches. Root titles use the latest trusted `updated_at` title, subagents retain
+their reported nickname plus parent title, projects prefer the canonical
+repository name over a directory fallback, and recent-task ordering uses the
+maximum activity observed across a complete lineage. A strictly exact
+active/archive pair is deduplicated only after both copies are verified and
+their safe signatures agree; any other repeated session is ambiguous and keeps
+identity coverage incomplete rather than guessing. This stable ambiguity is a
+data-quality state, not unfinished I/O: once base and required period coverage
+are complete, it no longer leaves the dashboard labelled as still indexing.
+
+The five structural call proxies are `patchCalls`, `toolCalls`,
+`postPatchToolCalls`, `compactCount`, and `taskCompleteCount`. They describe
+observed structural envelopes only, not file, command, or review counts. They
+produce no dollar cost and are never derived from prompt, response, command
+body, or tool-argument content.
+
+## Refresh and scale
+
+Claude polling always honors `refreshInterval`; its file watcher uses the
+configured quiet debounce. The production Claude path maintains an in-memory
+per-file index: unchanged refreshes read zero JSONL bodies, appends read only a
+verified tail, and truncate/replace/move/delete changes rebuild only affected
+files and aggregate groups. Content-analysis contributions and the established
+cross-file response-identity rules are updated through the same atomic path.
+A new Extension Host performs one cold in-memory build; watcher-driven refreshes
+do not reread and reaggregate the complete corpus. Codex uses its own quiet
+debounce (default 30 seconds, configurable to Off/10/30/60/120/300).
+
+Codex history is designed for multi-gigabyte local corpora:
+
+- discovery and parsing run outside the Extension Host in a worker;
+- files are indexed recent-first with progress and cancellation; an incomplete
+ backfill uses up to half of the available logical CPUs, capped at six local
+ file-pass workers, while completed indexes return to the single low-power
+ coordinator path;
+- unchanged warm refresh reads no JSONL body;
+- a first non-empty index or incomplete legacy migration gets one bounded
+ 16,384 file passes / 64 GiB streaming ceiling; this is not an up-front memory
+ allocation and retains cancellation and atomic resume checkpoints. After
+ convergence, automatic work uses 64 file passes / 128 MiB and the
+ always-visible manual Refresh uses 512 file passes / 2 GiB. The safe minimum
+ is 1 MiB + 1 byte, reads use 1 MiB chunks, and a Codex JSONL line is capped at
+ 1 MiB;
+- live progress remains frequent, while a large persisted snapshot is written
+ at most roughly every 10 seconds, 2 GiB, or 256 completed file passes, plus
+ the final stage boundary. This bounds crash recovery without letting repeated
+ tens-of-megabytes snapshots dominate a fast backfill;
+- derived lineage is reconciled only in stages that can change it; period and
+ stable stages reuse the verified relationship instead of repeatedly scanning
+ the complete index;
+- current-day hourly work is limited to canonical files already known from
+ period slices to contain `asOfDay`; its own checkpoints resume independently
+ and do not reset the primary index;
+- append refresh reads only the new tail; an incomplete line stays only in the
+ scanner's short-lived memory and is retried from the safe cursor, never in v3;
+- truncation/replacement reparses only the affected file;
+- cancellation checkpoints atomically save per-file contributions and migration
+ progress, so the next run resumes from the verified cursor;
+- concurrent refresh requests share one worker run.
+
+Weekly API-equivalent history is derived from already-aggregated token usage.
+Observed weekly resets align seven-day buckets; otherwise usage-only history
+uses Monday-to-Monday UTC calendar weeks. A token log can prove used value, but
+not an historical subscription capacity: full and unused estimates are emitted
+only where a real quota-utilization sample exists. Codex usage-only history may
+combine multiple sign-ins in one home, while quota-derived rows remain bound to
+their observed reset series.
+
+v2.3.0 does not infer a $20, $100, or $200 subscription tier. Local Codex logs
+do not expose a reliable account-and-plan identity, so a future comparison must
+use an explicit opt-in account mapping rather than attaching prices by guess.
+
+## Release invariants
+
+- Strict TypeScript, red-green TDD, full `node:test`, F5 smoke test, and installed
+ VSIX smoke test are required in proportion to the change.
+- User-visible strings cover `en`, `de-DE`, `zh-TW`, `zh-CN`, `ja`, `ko`,
+ `pt-BR`, and `id`; all seven README editions move together.
+- `package.json` is not manually version-bumped. Publishing the reviewed Release
+ Drafter draft creates the tag; the publish workflow stamps that tag version.
+- Contributor PR attribution is preserved by merging the contributor's original
+ PR or, with authorization, adjusting that PR branch before merge.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a922a616..dce931ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,292 @@ All notable changes to this fork compared to upstream
[`ClaudeCodeUsage/ClaudeCodeUsage`](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage) (last
upstream release: 1.0.8). Format follows [Keep a Changelog](https://keepachangelog.com).
+## [2.3.0] — Unreleased
+
+### Fixed
+- **Codex Today is now the configured calendar day** — the first Codex tab now
+ pairs its day total with exact hourly API-equivalent cost and a separate
+ token-composition view. Its additive schema-3 current-day sidecar scans only
+ canonical files already known to contain that day, checkpoints and resumes,
+ and does not trigger a full-history reindex. Daily and monthly primary trends
+ now default to API-equivalent cost while token composition remains separate;
+ unknown models stay unpriced and pricing coverage remains visible. Claude and
+ Codex time-series charts keep aligned responsive widths, with dense content
+ scrolling inside its own keyboard-focusable region.
+- **Weekly API-equivalent periods no longer overlap or double-count usage** —
+ the newest valid official reset observation anchors one sequence of unique
+ `[start, reset)` weekly buckets, so each local usage event contributes to
+ exactly one period. Any overlapping, non-aligned future reset is treated as a
+ conflict even when its series name differs; it cannot create an additional
+ "current" row. Codex usage is persisted in daily slices, so a slice that
+ crosses an official intraday reset remains counted once but marks the affected
+ period as a boundary approximation and suppresses total / unused allowance
+ inference. This display-only correction does not change the index schema or
+ trigger a rebuild. Historical Codex periods are always used-value-only; only
+ the newest current period may infer a total when its reset is unambiguous and
+ its indexed usage can be attributed to one observation source. Current unused
+ value remains withheld, and ambiguous multi-sign-in usage stays used-only.
+- **Claude changed-file refreshes no longer reread the full corpus (#87)** —
+ the production refresh path now keeps an exact in-memory per-file index,
+ reads only a verified append tail, and rebuilds only affected files for
+ truncate, replacement, move, and delete events. Cross-file response identity,
+ request-ID degradation, content UUID ownership, titles, context, sessions,
+ projects, branches, workflows, costliest messages, and all time buckets retain
+ the established full-loader results. Dashboard aggregates are materialized
+ incrementally, so an unchanged watcher refresh reads zero JSONL bodies and
+ performs zero aggregate mutations. A new Extension Host still performs one
+ cold in-memory build; normal runtime changes no longer repeat that work.
+- **Codex one-time backfills now use high-end hardware** — incomplete indexes
+ use an adaptive local pool of up to half the logical CPUs, capped at six
+ workers, for independent file passes. One-MiB stream chunks, stage-aware
+ lineage reconciliation, one durable write for a small warm append, and
+ coarser resumable checkpoints prevent repeated tens-of-megabytes index writes
+ from dominating the scan. Once complete, refresh returns to the bounded
+ low-power incremental path. Stable duplicate-session ambiguity is now shown
+ as an explicit data-quality warning and no longer leaves the page falsely
+ labelled as still indexing forever.
+- **Codex request-level token attribution** — valid `last_token_usage` snapshots
+ now provide the exact input, cached-input, output, and reasoning components;
+ their `total_tokens` value remains an active-context measurement rather than
+ request usage. A full numeric total-plus-last signature suppresses only a
+ replay from the same pseudonymous rate-limit source or an immediately adjacent
+ duplicate. Missing last snapshots retain the cumulative lineage high-water
+ fallback. Existing schema-3 indexes rebuild once, keep showing indexed
+ subtotals during that pass, and never mix the two attribution semantics.
+- **Conservative Codex rebuild totals** — schema and lineage migrations no
+ longer expose retained legacy aggregates as current usage. Cards, tables,
+ projects, sessions, recommendations, and the status bar now use only freshly
+ indexed contributions and show an indexed-subtotal notice until coverage
+ converges. Stale files remain visible in data-quality reporting. Account
+ limits remain last-observed snapshots and are never summed.
+- **Profile-scoped Claude quota credentials (#89)** — quota reads and token
+ refresh writes now follow explicit `dataDirectory`, then the first valid
+ `CLAUDE_CONFIG_DIR`, then `~/.claude`. A selected custom profile without a
+ credentials file shows quota as unavailable instead of silently using the
+ single global macOS Keychain account, and persisted quota snapshots are
+ isolated by profile. Thanks to [@HoangJN](https://github.com/HoangJN) for the
+ precise report.
+- **Per-model weekly limits are read again** — Anthropic's usage API stopped
+ filling in its per-model quota fields, so the weekly Opus figure had silently
+ gone blank. The extension now reads whichever per-model weekly cap your plan
+ meters and labels it the way Anthropic does, for example "Fable".
+- **The quota tooltip follows the reset countdown format** — it always used
+ whole units, so on the default decimal setting the same window read "4.5h" in
+ the status bar but "4h 29m" in the tooltip. Every row now reads "time left
+ (wall clock)" on one line, the 5-hour window included, which previously showed
+ no reset time at all.
+- **Missing settings translations** — four Brazilian Portuguese entries and one
+ Indonesian entry showed English text in the settings panel.
+
+### Added
+- **Codex API-equivalent cost summary** — every Codex usage scope now begins
+ with a clearly labelled approximate dollar value calculated from currently
+ indexed tokens and exact known-model API prices. Unknown models stay unpriced,
+ the hover text reports priced-model coverage, and the value is explicitly not
+ presented as a bill or subscription charge.
+- **Historical weekly allowance value** — Claude and Codex All-time and Compare
+ views now calculate historical used API-equivalent value directly from local
+ token logs. One valid observed reset anchors unique, non-overlapping weekly
+ buckets; without one, usage-only rows use Monday-to-Monday UTC calendar weeks.
+ Codex historical periods, boundary-approximate daily slices, conflicting reset
+ sequences, and usage that cannot be assigned to one observed sign-in remain
+ used-value-only. Only an unambiguous newest current period with single-source
+ attribution may infer a total allowance; its unused value is still withheld.
+ Current official API prices are applied consistently and each period includes
+ model-price coverage. This remains an estimate, not a bill or an official
+ subscription price. The panel is enabled by default and can be hidden with
+ `showWeeklyEquivalentValue`.
+- **Complete Claude quota details** — the tooltip now shows every active
+ all-model and model-scoped weekly cap reported by Anthropic, plus used monthly
+ credits when available. Model-scoped status-bar display remains opt-in and is
+ named dynamically instead of assuming Opus.
+- **Codex Beta** — local-only Codex usage views for processed, uncached input +
+ output, cached input, input cache-hit rate, output, reasoning, model, effort,
+ thread structure, index coverage, quality flags, and last-observed limit
+ snapshots.
+- **Provider-aware dashboard** — Claude, Codex Beta, and side-by-side Compare
+ modes preserve provider-specific semantics; Compare does not sum cost or quota.
+- **Immediate Codex entry during backfill** — once an allowed Codex home is
+ detected, its provider tab appears before the first index finishes and shows
+ exact indexed-file, percentage, and byte progress inside the page. The last
+ atomic checkpoint is hydrated before the worker starts, so its full indexed-
+ subtotal dashboard remains usable while reconciliation continues; a brand-new
+ index adopts its first checkpoint without waiting for the whole pass. Progress
+ renders are coalesced so the indicator does not turn backfill into a Webview
+ redraw loop. Compare still waits until both providers have real data.
+- **Provider-aware Codex dashboard** — the existing Today / Month / All time /
+ Sessions / Projects / Content / Settings render functions now accept a
+ provider and present the corresponding Codex calendar Today with hourly detail /
+ Last 30 days / All time / Sessions / Projects / Recommendations / Settings data.
+- **Truthful Codex identities** — root tasks use the latest path-redacted local
+ thread title; child rows prefer their own real thread title, then fall back to
+ their reported nickname while displaying the parent/root title. If those are
+ also missing, localized neutral fallbacks are used. Projects use the Git
+ repository name (or a non-Git folder basename). Raw session IDs, repository
+ URLs, and full paths remain excluded.
+- **Codex detail tables and limits** — source-derived task/project names,
+ provider-native token columns, model/range filtering, sortable session and
+ project tables, and unexpired named limit windows make high usage traceable
+ without inventing Branches, Workflows, cost, or real-time subscription state.
+- **Local Codex optimization guidance** — structural signals explain unusually
+ high subagent, effort, approval-reviewer, tool-call, and cache overhead without
+ inspecting or retaining prompt, response, command, or tool-argument content.
+- **Scalable Codex indexing** — a cancellable background worker and persistent
+ per-file aggregate index support incremental progress, tail-only append reads,
+ resume, and zero unchanged usage-record/rollout JSONL body rereads on warm
+ refreshes. Incomplete one-time backfills may use the bounded adaptive local
+ file-pass pool; completed indexes do not retain those extra workers. This does
+ not include the exact `$CODEX_HOME/session_index.jsonl` title stream performed
+ on every refresh.
+- **One-pass initial Codex backfill** — the first non-empty index or an
+ incomplete legacy migration receives a bounded 64 GiB / 16,384-file-pass
+ streaming ceiling, with cancellation and atomic resume checkpoints. The
+ ceiling is not an up-front memory allocation. After convergence, automatic
+ work returns to 128 MiB / 64 file passes and the always-visible Refresh action
+ uses 2 GiB / 512 file passes. Unchanged warm refreshes still read zero
+ usage-record JSONL bodies.
+- **Codex UI release gate** — production-rendered coverage checks shared-tab
+ navigation, settings, charts, sorting, accessibility, responsive overflow,
+ stylesheet identity, and the invariant that Codex-rendered classes are a
+ subset of classes already emitted by the Claude dashboard.
+
+### Changed
+- **`showOpusWeekly` is now `showScopedWeekly`** — the setting no longer names a
+ single model, because the API says which model is capped. Your existing choice
+ carries over, and it stays opt-in and off by default.
+- **The quota tooltip lists every weekly cap** the API reports, each on its own
+ row with its own bar, whether or not the status bar is showing it. A per-model
+ cap can be the binding one, so it is always one hover away.
+- **The status bar nests a per-model cap in the weekly figure** — "wk 9%
+ (fable 17%)" with one countdown, rather than repeating the identical reset for
+ each cap. A cap that reset on its own schedule would still get its own segment.
+- **Caps with nothing to report stay hidden** — a per-model weekly cap appears
+ once it has usage against it, so it is absent at the start of a week rather
+ than sitting at 0%. The 5-hour and all-models figures always show.
+- **Reset times read to the minute** — they were shown truncated to the second,
+ so a cap resetting at 16:59:59 displayed as "16:59" while the cap it resets
+ alongside displayed "17:00".
+- **Quota warns at the same points as the official Claude app.**
+ The quota indicator and every bar in its tooltip now turn amber at 75% and red at 90%, instead of 80% and 95%.
+ The context-window indicator keeps the earlier 80% and 95% steps.
+- **Unified Claude/Codex dashboard shell** — Codex Beta now uses the same
+ header/action order, navigation rhythm, summary cards, detail rows, token
+ composition, tables, spacing, and responsive behavior as the existing Claude
+ dashboard. Usage limits are compact summary cards, recent-task identity is no
+ longer followed by a duplicate statistics block, and recommendation
+ composition uses the established model-detail layout.
+- **Consistent Codex terminology** — user-facing metrics now use Processed,
+ Input, Uncached input, Cached input, Output, Reasoning, and Uncached usage
+ consistently across all eight supported locales; internal field names and
+ persisted setting values remain compatible.
+- **Readable Codex Sessions table** — the collapsed view keeps eight useful
+ columns at 1280 px, prioritizes Thread and Project, and moves secondary token
+ details into an expandable row without duplicating Role.
+- **Stable dashboard UI state** — the selected provider, active tab, chart
+ metric, table sort, expanded rows, recommendation filters, and page scroll
+ position survive a webview reload when still applicable.
+- **Generic model-scoped weekly setting** — the earlier `showOpusWeekly` choice
+ migrates to `showScopedWeekly`, follows the model name supplied by Anthropic,
+ and nests a shared-reset cap into the weekly status-bar segment.
+- **One Claude/Codex dashboard render stack** — Codex Beta is now a provider
+ switch inside `webview.ts`. It uses the same render functions, HTML shell,
+ class names, stylesheet, header/action order, tabs, summary cards, detail
+ rows, charts, tables, spacing, and responsive behavior as Claude.
+- **Exact-version release announcements** — the default-on notification can be
+ disabled, stays quiet on a fresh install, and shows only the content for the
+ complete installed version instead of falling back to stale v2.2 notes.
+- Repository policy and architecture now define provider-neutral contracts,
+ Codex privacy boundaries, eight-locale/seven-README parity, and the real
+ OpenAI Codex co-author trailer for Codex-led commits.
+- Codex indexing follows observed rollout semantics: child counters start from
+ their own zero, repeated metadata preserves lineage, `guardian` sessions are
+ approval reviewers, and known non-usage envelopes are not quality failures.
+- The shared Settings renderer shows only shared and Codex-effective controls
+ when Codex is selected. Codex and its local optimization signals can be
+ disabled independently; recommendations remain grounded in the indexed
+ 30-day structural aggregates and disclose partial coverage.
+- Codex period charts now reuse the existing dashboard's Y axis, grid, theme
+ colors, horizontal scrolling, and metric-switching behavior.
+- **Persistent period indexing** — the compatible `codex-index-v1.json` path now
+ persists only sanitized aggregates, promotes exact local day slices in bounded
+ resumable batches, and exposes independent 7-day, 30-day, and all-time
+ coverage. All-time aggregates remain verified independently of partial period
+ slices; exact active/archive copies are deduplicated while ambiguous identities
+ remain visible as incomplete coverage.
+- Rolling 7-day and 30-day views now use exact event-day slices in the configured
+ timezone. Period migration and coverage gaps stay visibly partial instead of
+ being presented as complete data.
+- **Schema-3 lineage reconciliation** — ordered fingerprints of numeric token
+ counters remove only copied parent prefixes across direct, nested, and
+ multi-epoch forks and verified active/archive overlaps while preserving
+ independent sibling work. Missing parents remain conservatively counted and
+ surface a visible quality warning. Legacy indexes rebuild in bounded passes
+ instead of retaining inflated totals.
+
+### Fixed
+- **Claude response-level token counting** — regression fixtures now lock the
+ observed transcript behavior: one response may emit separate `thinking` and
+ `text` rows with the same `messageId`, `requestId`, and complete `usage`
+ vector, so the extension counts that response once and keeps its largest
+ vector rather than summing rows like Claude Code's `stats-cache`. Identity
+ degradation is also covered: a matching row that omits `requestId` still
+ joins the sole known request, while distinct request IDs stay separate.
+- **Shared-dashboard readability** — active tabs remain distinguishable in
+ Light+ and Dark+, sortable headers expose their interaction without changing
+ the established alignment, and the output segment in composition charts uses
+ a fully opaque registered VS Code theme color.
+- **Responsive first-pass automation** — when both configured model tiers fail
+ or return no usable text, the Issue/PR workflow posts a deterministic,
+ provider-neutral fallback instead of exiting without a comment; reruns avoid
+ duplicate first-pass replies.
+- **Consistent Claude quota reset detail** — tooltip countdowns follow the
+ selected format and pair it with the wall-clock reset, while zero-use scoped
+ caps stay out of the compact status bar.
+- **Codex idle energy and multi-window contention** — Unfocused VS Code windows
+ suspend Claude and Codex polling/watchers until focus returns. Complete,
+ unchanged Codex indexes now skip aggregate recomputation and disk writes;
+ cross-window refreshes share a single index lease, and atomic saves use unique
+ temporary files instead of competing for one `.tmp`.
+- **Stable rolling-period tests** — Codex index refreshes use an injectable clock
+ internally so recent-period coverage remains deterministic without changing
+ runtime date or timezone semantics.
+- **Codex index self-recovery** — malformed JSON and unsupported persisted index
+ schemas are atomically preserved as timestamped `.corrupt-*.json` backups,
+ then rebuilt from local usage records instead of leaving Codex Beta stuck in
+ an error state. Diagnostics expose only a safe recovery reason, never the
+ index path or contents; unrelated filesystem errors still fail closed.
+- **Codex fork overcounting** — copied token histories replayed into child
+ rollouts no longer inflate provider totals. Counter regressions use
+ exact last-request components with partial confidence; only the missing-last
+ cumulative fallback uses component-wise high-water containment rather than
+ adding reset gaps again.
+- **Visible Codex backfill state** — while bounded indexing is still converging,
+ Coverage · Quality now warns that current totals are incomplete, shows the
+ real indexed-files/total-files progress, and clears the warning automatically
+ once base and period coverage are complete.
+
+### Removed
+- **Weekly Opus naming retired** — the fixed Opus-specific surface is replaced
+ by the generic, API-named `showScopedWeekly` setting. PR #38 and
+ [@wheelbarrel00](https://github.com/wheelbarrel00) remain credited for the
+ original contribution.
+
+### Privacy
+- Codex usage-record discovery is restricted to `sessions/**/*.jsonl` and
+ `archived_sessions/**/*.jsonl`. Separately, the extension streams exactly
+ `$CODEX_HOME/session_index.jsonl` to map `id` to `thread_name` for truthful
+ thread titles; credentials, SQLite databases, browser/keychain state, and
+ unknown files remain excluded.
+- Absolute filesystem paths embedded in a Codex thread title are replaced with
+ `[path]`, and the sanitized title remains memory-only. Usage-record JSONL lines
+ are streamed and temporarily parsed only for allowlisted metadata; prompt,
+ response, command, and tool-argument fields are not inspected or used for
+ analysis and are never retained.
+- The persistent index contains machine-salted pseudonymous keys, numeric and
+ structural aggregates, and sanitized project, directory, agent, model,
+ effort, role, time, and quality metadata. It never persists raw IDs, full
+ paths or repository URLs, thread titles, or conversation bodies.
+
## [2.2.2] — Unreleased
### Fixed
@@ -19,7 +305,6 @@ upstream release: 1.0.8). Format follows [Keep a Changelog](https://keepachangel
- **Opus 5 context window (#81, reported in #84)** — recognise the bare
`claude-opus-5` model id as a 1M-context model and remove its spurious
unknown-model pricing diagnostic. Thanks [@e7d](https://github.com/e7d).
-
## [2.2.1] — 2026-07-18
### Added
diff --git a/CLAUDE.md b/CLAUDE.md
index 1d1af4ab..15e79f6c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,9 +6,16 @@ faithful maintainer review copy is `AGENTS.zh-CN.md`.
Claude-specific compatibility notes:
-- The product remains Claude-only in v2.2.1; Codex is a development tool.
+- From v2.3.0 the product monitors more than one provider: Claude is the
+ primary provider; Codex ships as a beta provider using local logs and clearly
+ labelled API-equivalent cost estimates, never a bill or subscription charge.
+ Codex is also a development tool for this repository — do not conflate the
+ two roles.
- Claude conversation JSONL is read-only. OAuth credential refresh is a
separate, existing security-sensitive behavior; do not broaden it casually.
+- Claude runtime usage refreshes go through `claudeIncrementalIndex.ts`; do not
+ restore the old full-corpus read and full-record aggregation path after each
+ watcher event.
- JSONL activity may tune the quota-cache TTL, but polling always follows
`refreshInterval`; do not restore a hidden active polling override.
- Do not create a second policy source here. Update `AGENTS.md` and its Chinese
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fbe99153..5e1a525e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,11 +7,22 @@ it grows.
## Project scope
Before proposing a feature, it helps to know the positioning: this extension is
-intentionally **Claude-only and lightweight**, prioritising **token-precision**
-(seeing where tokens go) over cost-precision, and using AI to help people use
-Claude Code better. Multi-provider monitoring and full billing reconciliation
-are explicitly out of scope. Features that sharpen token attribution or the
-advice experience are the best fit.
+provider-aware and lightweight, supporting Claude Code and **Codex Beta**.
+Codex Beta is enabled by default and can be turned off in provider settings.
+The emphasis is truthful local usage and token attribution rather than billing
+reconciliation. Features that sharpen attribution, privacy, or the advice
+experience are the best fit.
+
+Usage ingestion reads only each provider's local metadata and usage logs.
+Usage ingestion is read-only, and Codex data is never mutated. Codex does not estimate
+dollar cost. Its usage JSONL is streamed and temporarily parsed for allowlisted
+metadata; conversation fields are not inspected or used for analysis and are never
+retained. Codex limits are last-observed values from local logs, not real-time billing
+data.
+
+Claude session actions are separately gated and disabled by default. When enabled,
+they can resume or delete a selected session.
+Deleting the selected session moves its log to the OS trash.
## Development setup
@@ -23,29 +34,37 @@ npm run watch # recompile on change
- Press **F5** in VS Code to launch the Extension Development Host and test
your changes manually.
-- Package a `.vsix` with `npx @vscode/vsce package`.
-
-The extension reads `~/.claude/projects/**/*.jsonl` **read-only** — it never
-writes to your Claude data.
+- Package a `.vsix` with `npx -y @vscode/vsce@3.9.1 package`.
## Pull requests
- Keep each PR focused on one logical change.
-- Run `npm run compile` and make sure it's clean before opening the PR.
+- Run `npm test` and the relevant browser/host layers below before opening the
+ PR.
- Describe the problem and the fix; screenshots help for UI changes.
- If your change affects user-facing behaviour, update `CHANGELOG.md` and the
relevant parts of `README.md`.
## Tests
-Tests use **Node's built-in runner** (`node:test` + `node:assert`) — no extra
-dependencies. They run against the *compiled* output:
-
-```bash
-npm test # compiles, then runs node --test over out/test/*.test.js
-```
-
-CI runs the same command on every PR (`.github/workflows/test.yml`).
+Tests run in three layers:
+
+1. **Node logic and policy — `npm test`.** This runs the TypeScript compile,
+ then executes the Node logic and repository-policy tests against compiled
+ output with Node's built-in runner (`node:test` + `node:assert`).
+2. **Real Webview Chromium — `npm run test:ui`.** This compiles and exercises
+ the real, complete Webview in Chromium, covering interaction, reload state,
+ Axe accessibility, eight-locale overflow, and visual baselines.
+3. **VS Code host smoke.** Press **F5** or install a packaged VSIX. Verify real
+ VS Code host activation, real local metadata, and theme smoke behavior. This
+ layer does not replace the first two layers.
+
+CI uses separate gates, with packaging after both test jobs pass on every PR
+(`.github/workflows/test.yml`). Canonical Linux screenshots may only be updated
+with the exact official container
+`mcr.microsoft.com/playwright:v1.61.1-noble`, with
+`PLAYWRIGHT_BROWSERS_PATH=/ms-playwright`. A macOS run must not overwrite a
+Linux snapshot; reproduce and update it in that exact Linux container.
**Where tests live.** Put test files **directly** in `src/test/`, named
`*.test.ts`. Because `tsc` is configured with `rootDir: src`, they compile to
@@ -54,8 +73,8 @@ packaged `.vsix`. Keep the directory flat — the `test` script globs
`out/test/*.test.js` (a single level, so it stays portable across Node
versions), which means files in nested subfolders would be silently skipped.
-**What to test.** This harness is for **pure, dependency-free logic** — modules
-that don't import the `vscode` API:
+**What to test in the Node layer.** Keep modules under direct test independent
+of the live `vscode` API. Existing examples include:
- pricing & cost calculation (`pricing.ts`)
- aggregation: daily / weekly / monthly / all-time (`dataLoader.ts`)
@@ -67,12 +86,9 @@ is a worked example to copy from. Prefer asserting on **observable behaviour**
(a computed cost, a resolved pricing tier) over implementation details, and add
a case for the tricky edge you just fixed so it can't regress.
-**What doesn't belong here.** Anything that touches the live `vscode` API
-(status-bar wiring, webview, commands) needs the heavier
-[`@vscode/test-electron`](https://github.com/microsoft/vscode-test) harness,
-which isn't set up yet. If you need it, raise it on
-[#25](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/issues/25) first so we keep the
-two harnesses cleanly separated.
+Webview behavior belongs in `tests/ui/` and must use the production rendering
+harness rather than synthetic HTML. Extension activation, commands, and real
+local-provider integration receive the VS Code host smoke layer.
## Releases
@@ -110,7 +126,7 @@ missing or empty PR diff stops the workflow without posting a review.
Codex automatic attribution is not enabled in v2.2.1. It requires a separately
implemented and trusted OpenAI/Codex transport; a model name or repository
-variable alone cannot enable it.
+variable alone cannot enable it. Codex automatic attribution remains disabled in v2.3.0.
### Maintainer-reviewed Codex text
@@ -128,8 +144,10 @@ This means the maintainer reviewed the text. It does not add a synthetic
### Maintainer-only mention agent
`.github/workflows/claude.yml` is a separate, maintainer-gated Claude Code
-workflow with repository write permissions. v2.2.1 does not migrate this privileged workflow to Codex. Treat every assistant reply as a starting point,
-not repository authority; the maintainer makes the final decision.
+workflow with repository write permissions. v2.2.1 does not migrate this privileged workflow to Codex.
+v2.3.0 still does not migrate this privileged workflow to Codex. Treat every
+assistant reply as a starting point, not repository authority; the maintainer
+makes the final decision.
## Code of conduct
diff --git a/README-en.md b/README-en.md
index 2951d52a..043b98d1 100644
--- a/README-en.md
+++ b/README-en.md
@@ -4,7 +4,7 @@
---
-**The Claude Code coach in your status bar.** Not a billing tool. Not a multi-provider monitor. A focused token tracker that uses AI to help you use Claude Code better.
+**The local Claude Code and Codex usage coach in your status bar.** Not a billing tool. Claude retains its cost and quota views; Codex Beta adds token and behaviour insights with Codex-native semantics.
> **What it is:** a VS Code status-bar monitor that reads your local Claude Code conversation logs and shows **token-derived** usage and cost estimates — plus an optional AI advisor that suggests how to improve your prompts and reduce waste.
>
@@ -36,6 +36,20 @@ Hover the quota indicator for a breakdown:
- **Multi-vendor pricing** — Opus 4.x / Sonnet 4.x / Haiku 4.5 verified against Anthropic's public pricing; reference rates for OpenAI / Gemini / DeepSeek / Kimi / GLM / Qwen with family-aware fallback. `Refresh Token Pricing` pulls live LiteLLM data.
- **Personalisation** — language, timezone, decimal places, compact numbers, project grouping, dashboard auto-refresh toggle.
+## Codex Beta in v2.3
+
+- Codex usage records are discovered only from `sessions/**/*.jsonl` and `archived_sessions/**/*.jsonl`; credential, database, and unknown files remain excluded. Separately, the extension streams exactly `$CODEX_HOME/session_index.jsonl` to map `id` to `thread_name` for truthful thread titles. Absolute paths are redacted and titles remain memory-only. Usage-record JSONL lines are streamed and temporarily parsed only to extract allowlisted usage and structural metadata; prompt, response, command, and tool-argument fields are not inspected or used for analysis, and are never retained or persisted.
+- **Processed** means input + output, **uncached usage** means uncached input + output, **cached input** remains a subset of input, and reasoning remains a subset of output. The overview also shows input cache hit rate as cached input / input. Codex billing cost is not shown. Its first summary card is a clearly labelled API-equivalent cost estimate for the selected scope, and the All-time view shows the weekly trend on the same pricing basis; Claude / Codex / Compare keep each provider's accounting separate.
+- Codex **Today** means the current calendar day in the configured timezone. It adds exact hourly API-equivalent cost beside a separate token-composition view; daily and monthly primary charts also default to API-equivalent cost while token composition stays separately visible. Only exact known-model prices contribute, so unknown models remain unpriced and pricing coverage stays visible. The additive, schema-3-compatible hourly sidecar processes only canonical files already known to contain today, is checkpointed and resumable, and does not force a full-history reindex.
+- Request-level attribution prefers valid `last_token_usage` components; its `total_tokens` is active-context size, not request usage. A full numeric total-plus-last signature suppresses only proven replay from the same pseudonymous rate-limit source or an immediately adjacent duplicate. Missing last snapshots fall back to cumulative lineage high-water. Upgrading triggers one automatic reindex, while the indexed subtotal remains visible throughout the pass.
+- Claude and Codex All-time / Compare views calculate historical used equivalents directly from local token logs. The newest valid official reset observation anchors one sequence of unique, non-overlapping weekly periods, so each usage event is counted once; without a usable observation, usage-only rows fall back to Monday-to-Monday UTC calendar weeks. Any overlapping, non-aligned future reset is a conflict even when its series name differs; it cannot create another current period, and period ranges are shown separately from reset times. Codex usage is stored in daily slices: when one crosses an official intraday reset, its tokens are still counted once, the affected period is labelled a boundary approximation, and only used equivalent is shown. This display rule does not change the index schema or trigger a rebuild. Codex historical periods are always used-value-only. Only the newest current period may infer a total when the reset is unambiguous and indexed usage can be attributed to one observation source; current unused value remains withheld. Multi-sign-in usage that cannot be attributed reliably also stays used-only, without inventing an account split. Current official API rates are applied consistently across history. This is a proxy, not a bill or an official subscription price. The panel is enabled by default and can be hidden in Settings with `showWeeklyEquivalentValue`. Claude records profile-scoped quota observations from this release onward.
+- Switching to Codex keeps the established Today / Month / All time / Sessions / Projects / Content / Settings structure, relabelled where Codex semantics differ. Both providers use the same render functions, HTML classes, charts, tables, spacing, and responsive rules; their time-series charts stay width-aligned while dense content scrolls inside its own region. Codex recommendations use indexed 30-day structural evidence.
+- Root tasks use the latest real thread title after path redaction. Child rows prefer their own real thread title; when it is missing, they use the reported nickname and display the parent/root title; if those are also missing, they receive a localized neutral fallback. Project names use the Git repository name, or the directory basename outside Git. The extension does not invent Branches or Workflows that cannot be measured reliably.
+- Rolling 7-day and 30-day values use exact event-day slices in the configured timezone. During an incomplete migration or rebuild, every Codex card, table, project, session, recommendation, and status value is visibly an **indexed subtotal**; unverified legacy totals are excluded. Once a Codex home is detected, its provider tab appears immediately; the page shows exact indexed-file, percentage, and byte progress during the first build, while Compare waits until both providers have real data. After the first atomic checkpoint, the complete subtotal dashboard stays usable while indexing continues; progress never replaces its cards or tables. A selected Codex home is account-agnostic, so logs left by multiple sign-ins in that same home are combined. Local logs expose no reliable account identity, so limit cards remain **last-observed** and are never summed.
+- Each recommendation presents an observation, readable evidence, and a conditional action only when the indexed 30-day structural aggregates support it; no evidence means no generic advice.
+- The persistent index stores machine-salted pseudonymous keys; numeric and structural aggregates; and sanitized project, directory, agent, model, effort, role, time, and quality metadata. It never stores raw IDs, full paths or repository URLs, thread titles, or conversation bodies.
+- The shared Settings tab shows only common and Codex-effective controls when Codex is selected. Codex collection and local Codex recommendations can be disabled independently; the background watcher delay is configurable (30 seconds by default, with Off and longer intervals available). A first-time index or incomplete legacy migration gets one bounded 64 GiB / 16,384-file-pass streaming ceiling; it does not reserve that amount of memory and remains cancellable and resumable. After convergence, background work returns to 128 MiB / 64 file passes and the always-visible Refresh action uses 2 GiB / 512 file passes. Unchanged warm refreshes still read zero usage-record JSONL body bytes.
+
## Install
Search for **`Claude Code Usage`** in the Extensions view (`Ctrl+Shift+X`), or:
@@ -64,10 +78,19 @@ See the [full settings table in the main README](README.md#configuration).
**"No Claude Code Data"** — make sure Claude Code is installed and used at least once; check the `dataDirectory` setting (auto-detection looks at `~/.claude/projects`).
-**Quota shows `5h:--% wk:--%`** — log in to Claude Code once; the extension reads `~/.claude/.credentials.json` read-only.
+**Quota shows `5h:--% wk:--%`** — log in to the active Claude profile once.
+Credentials follow explicit `dataDirectory`, then the first valid
+`CLAUDE_CONFIG_DIR`, then `~/.claude`; the global macOS Keychain item is used
+only for the default profile.
**Usage history is missing older months** — Claude Code deletes logs older than `cleanupPeriodDays` (default 30). To keep more, set `{ "cleanupPeriodDays": 365 }` in `~/.claude/settings.json`. Already-deleted logs can't be recovered.
+**Token counts are lower than Claude Code's `stats-cache`** — one response can
+produce separate `thinking` and `text` transcript rows with the same
+`messageId`, `requestId`, and complete `usage` vector. The extension counts the
+response identity once and keeps its largest vector; Claude Code's `stats-cache`
+sums the rows. The extension does not multiply its total to match that cache.
+
**Token counts lower than your provider's dashboard** — some proxies / dynamic workflows write per-agent records to sub-directories that may be incomplete. Your actual spend is on your provider's billing page. Native workflow attribution is planned.
**High CPU or sluggish refresh on a large history (Linux included)**
diff --git a/README-id.md b/README-id.md
index 0b6e4649..942600be 100644
--- a/README-id.md
+++ b/README-id.md
@@ -4,7 +4,7 @@
---
-**Pelatih Claude Code Anda di status bar.** Bukan alat billing. Bukan monitor multi-provider. Pelacak token yang fokus, dan memakai AI untuk membantu Anda memakai Claude Code dengan lebih baik.
+**Pelatih penggunaan lokal Claude Code dan Codex di status bar.** Bukan alat billing. Tampilan biaya / kuota Claude tetap ada; Codex Beta menganalisis token dan perilaku sesuai semantik Codex.
> **Apa ini:** monitor status bar VS Code yang membaca log percakapan Claude Code lokal Anda dan menampilkan estimasi penggunaan serta biaya **berbasis token** — plus penasihat AI opsional yang menyarankan cara memperbaiki prompt Anda dan mengurangi pemborosan.
@@ -36,6 +36,19 @@ Arahkan kursor ke indikator kuota untuk melihat rinciannya:
- **Harga multi-vendor** — Opus 4.x / Sonnet 4.x / Haiku 4.5 diverifikasi terhadap harga publik Anthropic; tarif referensi untuk OpenAI / Gemini / DeepSeek / Kimi / GLM / Qwen dengan fallback berbasis family model. `Refresh Token Pricing` menarik data LiteLLM langsung.
- **Personalisasi** — bahasa, zona waktu, angka desimal, angka ringkas, pengelompokan proyek, toggle penyegaran otomatis dashboard.
+## Codex Beta di v2.3
+
+- Catatan penggunaan Codex hanya ditemukan dari `sessions/**/*.jsonl` dan `archived_sessions/**/*.jsonl`; file kredensial, database, dan file tak dikenal tetap dikecualikan. Secara terpisah, ekstensi hanya melakukan streaming terhadap `$CODEX_HOME/session_index.jsonl` untuk memetakan `id` ke `thread_name` bagi judul thread yang sebenarnya. Jalur absolut dalam judul disamarkan dan judul hanya disimpan di memori. Setiap baris JSONL penggunaan di-stream dan diparse sementara hanya untuk mengambil metadata penggunaan dan struktur dalam daftar izin; field prompt, respons, perintah, dan argumen alat tidak diperiksa atau dipakai untuk analisis, serta tidak pernah disimpan atau dipersistenkan.
+- **Diproses** = input + output, **Penggunaan tanpa cache** = Input tanpa cache + output, **cached input** tetap bagian dari input, dan reasoning bagian dari output. Ringkasan juga menampilkan tingkat hit cache input sebagai cached input / input. Biaya tagihan Codex tidak ditampilkan. Kartu ringkasan pertama menampilkan estimasi biaya ekuivalen API untuk cakupan terpilih dengan label yang jelas, sedangkan tampilan Sepanjang Waktu menunjukkan tren mingguan dengan dasar harga yang sama. Claude / Codex / Compare tetap memisahkan makna tiap penyedia.
+- **Hari Ini** pada Codex berarti hari kalender saat ini dalam zona waktu yang dikonfigurasi. Tampilan ini menambahkan biaya ekuivalen API per jam yang tepat di samping tampilan komposisi Token yang terpisah. Grafik utama harian dan bulanan juga memakai biaya ekuivalen API sebagai default, sementara komposisi Token tetap tersedia secara terpisah. Hanya harga model yang dikenal dan cocok tepat yang dihitung; model tak dikenal tetap tanpa harga dan cakupan harga selalu terlihat. Sidecar per jam yang bersifat tambahan dan kompatibel dengan schema 3 hanya memproses file canonical yang sudah diketahui memuat penggunaan hari ini, dapat dilanjutkan dari checkpoint, dan tidak memicu pengindeksan ulang seluruh riwayat.
+- Atribusi Token per permintaan memprioritaskan component `last_token_usage` yang valid; `total_tokens` di dalamnya adalah ukuran konteks aktif, bukan penggunaan permintaan. Signature numerik lengkap total-plus-last hanya menghapus replay yang terbukti berasal dari sumber rate-limit pseudonim yang sama atau record yang tepat bersebelahan. Jika last snapshot tidak ada, parser kembali ke cumulative lineage high-water. Setelah upgrade, indeks dibangun ulang otomatis satu kali dan subtotal terindeks tetap terlihat selama prosesnya.
+- Tampilan Sepanjang Waktu / Perbandingan Claude dan Codex menghitung ekuivalen terpakai historis langsung dari log Token lokal. Pengamatan reset resmi terbaru yang valid menjadi satu jangkar bagi rangkaian periode mingguan yang unik dan tidak tumpang tindih, sehingga setiap peristiwa penggunaan hanya dihitung dalam satu periode. Jika tidak ada pengamatan yang dapat dipakai, riwayat khusus pemakaian kembali ke minggu kalender UTC Senin-ke-Senin. Reset masa depan yang tumpang tindih tetapi tidak selaras dianggap konflik meskipun nama series berbeda; reset itu tidak dapat membuat periode berjalan kedua. Rentang periode dan waktu reset ditampilkan terpisah. Penggunaan Codex disimpan sebagai irisan harian. Bila suatu irisan melewati reset resmi di tengah hari, Token tetap dihitung satu kali, periode terdampak diberi label 「perkiraan batas」, dan hanya ekuivalen terpakai yang ditampilkan tanpa menyimpulkan total batas atau sisa. Aturan tampilan ini tidak mengubah schema indeks dan tidak memicu pembangunan ulang. Periode historis Codex selalu hanya menampilkan nilai terpakai. Hanya periode berjalan terbaru yang boleh memperkirakan total batas bila reset tidak berkonflik dan penggunaan terindeks dapat diatribusikan dengan andal ke satu sumber pengamatan; nilai sisa periode berjalan tetap tidak ditampilkan sebagai kesimpulan. Penggunaan dari beberapa login yang tidak dapat diatribusikan juga tetap hanya menampilkan nilai terpakai tanpa mengarang pemisahan akun. Harga API resmi saat ini diterapkan konsisten pada riwayat. Ini proksi, bukan tagihan atau harga langganan resmi. Panel ini aktif secara default dan dapat disembunyikan di Settings dengan `showWeeklyEquivalentValue`. Claude mulai merekam pengamatan batas per profil sejak rilis ini.
+- Beralih ke Codex tetap memakai struktur Hari Ini / Bulan Ini / Sepanjang Waktu / Sesi / Proyek / Konten / Settings yang sudah ada, dengan label yang disesuaikan pada makna Codex. Kedua penyedia memakai render function, HTML class, grafik, tabel, jarak, dan aturan responsif yang sama; grafik deret waktunya tetap sejajar lebarnya, sedangkan konten padat hanya bergulir di areanya sendiri. Rekomendasi Codex memakai bukti struktural 30 hari yang telah diindeks.
+- Tugas utama memakai judul thread nyata terbaru setelah jalur disamarkan. Baris tugas anak memprioritaskan judul thread nyatanya sendiri; jika tidak ada, baris memakai nama panggilan yang dilaporkan sambil menampilkan judul induk / tugas utama. Jika informasi itu juga tidak ada, baris memakai nama pengganti netral yang dilokalkan. Nama proyek memakai nama repositori Git, atau nama folder di luar Git. Ekstensi tidak mengarang Branches atau Workflows yang tidak dapat dihitung secara andal.
+- Nilai 7 dan 30 hari memakai irisan tepat berdasarkan hari peristiwa di zona waktu yang dikonfigurasi. Saat migrasi atau pembangunan ulang belum selesai, setiap kartu, tabel, proyek, sesi, rekomendasi, dan nilai status Codex ditandai sebagai **subtotal terindeks**, sementara total lama yang belum diverifikasi dikecualikan. Setelah Codex home terdeteksi, tab penyedianya langsung ditampilkan; selama indeks pertama dibuat, jumlah file terindeks yang tepat, persentase, dan progres kapasitas tampil langsung di halaman, sedangkan Perbandingan menunggu hingga kedua penyedia memiliki data nyata. Setelah checkpoint atomik pertama dibuat, seluruh dasbor subtotal tetap dapat digunakan saat pengindeksan berlanjut; indikator progres tidak menggantikan kartu atau tabel. Codex home yang dipilih tidak membedakan akun, jadi log dari beberapa login di home yang sama digabung. Log lokal tidak memiliki identitas akun yang andal; kartu batas tetap **terakhir diamati** dan tidak dijumlahkan.
+- Setiap rekomendasi hanya menampilkan pengamatan, bukti yang mudah dibaca, dan tindakan bersyarat bila agregat struktural 30 hari yang telah diindeks mendukungnya. Tanpa bukti, tidak ada saran umum.
+- Indeks persisten menyimpan kunci pseudonim dengan salt khusus mesin; agregat numerik dan struktural; serta metadata proyek, direktori, agen, model, effort, peran, waktu, dan kualitas yang telah disanitasi. Indeks tidak pernah menyimpan ID mentah, jalur lengkap atau URL repositori, judul thread, maupun isi percakapan.
+- Tab Settings bersama hanya menampilkan kontrol umum dan kontrol yang berlaku untuk Codex saat Codex dipilih. Pengumpulan Codex dan rekomendasi Codex lokal dapat dinonaktifkan secara terpisah; jeda watcher latar dapat diatur (default 30 detik, tersedia Off dan interval lebih panjang). Indeks pertama atau migrasi indeks lama yang belum tuntas mendapat satu batas streaming 64 GiB / 16.384 lintasan file; kapasitas itu tidak dialokasikan di memori di muka serta tetap dapat dibatalkan dan dilanjutkan. Setelah tuntas, kerja latar kembali ke 128 MiB / 64 lintasan file dan Refresh yang selalu terlihat memakai 2 GiB / 512 lintasan file. Refresh biasa tanpa perubahan tetap membaca nol isi JSONL penggunaan.
## Instalasi
Cari **`Claude Code Usage`** di tampilan Extensions (`Ctrl+Shift+X`), atau:
@@ -64,10 +77,20 @@ Lihat [tabel pengaturan lengkap di README utama](README.md#configuration).
**"No Claude Code Data"** — pastikan Claude Code sudah terpasang dan pernah dipakai minimal sekali; periksa pengaturan `dataDirectory` (deteksi otomatis mencari di `~/.claude/projects`).
-**Kuota menampilkan `5h:--% wk:--%`** — login ke Claude Code sekali; extension ini membaca `~/.claude/.credentials.json` secara baca-saja.
+**Kuota menampilkan `5h:--% wk:--%`** — login ke profil Claude yang aktif.
+Kredensial mengikuti `dataDirectory` eksplisit, lalu `CLAUDE_CONFIG_DIR` valid
+pertama, lalu `~/.claude`; item Keychain macOS global hanya dipakai untuk profil
+default.
**Riwayat penggunaan bulan-bulan lama hilang** — Claude Code menghapus log yang lebih lama dari `cleanupPeriodDays` (default 30). Untuk menyimpan lebih lama, atur `{ "cleanupPeriodDays": 365 }` di `~/.claude/settings.json`. Log yang sudah terhapus tidak bisa dipulihkan.
+**Jumlah token lebih rendah daripada `stats-cache` Claude Code** — satu respons
+dapat ditulis sebagai baris transkrip `thinking` dan `text` yang terpisah dengan
+`messageId`, `requestId`, dan vektor `usage` lengkap yang sama. Extension ini
+menghitung identitas respons tersebut satu kali dan menyimpan vektor terbesarnya;
+`stats-cache` Claude Code menjumlahkan setiap baris. Extension tidak memakai
+faktor pengali agar hasilnya menyerupai cache itu.
+
**Jumlah token lebih rendah dari dashboard provider Anda** — beberapa proxy / dynamic workflow menulis catatan per-agent ke sub-direktori yang mungkin tidak lengkap. Pengeluaran sebenarnya ada di halaman billing provider Anda. Atribusi workflow native sedang direncanakan.
## Kredit
diff --git a/README-ja.md b/README-ja.md
index 2ea4a025..ede16bdd 100644
--- a/README-ja.md
+++ b/README-ja.md
@@ -4,7 +4,7 @@
---
-**Claude Code をより賢く使うための、ステータスバーコーチ。** 請求ツールでも、マルチプロバイダー監視でもありません。AI を活用してトークン消費を分析し、使い方を改善する軽量 VS Code 拡張機能です。
+**Claude Code と Codex のローカル使用量を改善するステータスバーコーチ。** 請求ツールではありません。Claude のコスト / クォータ表示を維持し、Codex Beta は Codex 固有のトークンと動作の意味で分析します。
> **これは何か**:ローカルの Claude Code 会話ログを読み取り、**トークンから算出した**使用量とコストの見積もりを表示する VS Code ステータスバーモニター。さらに、プロンプトの改善と無駄削減を提案する任意の AI アドバイザー付き。
>
@@ -36,6 +36,20 @@
- **マルチベンダー価格** — Opus 4.x / Sonnet 4.x / Haiku 4.5 は Anthropic 公式価格で検証済み。OpenAI / Gemini / DeepSeek / Kimi / GLM / Qwen の参考価格(ファミリー認識フォールバック付き)。`Refresh Token Pricing` で LiteLLM の最新価格を取得。
- **パーソナライズ** — 言語、タイムゾーン、小数点桁数、コンパクト数値、プロジェクトのグループ化、ダッシュボード自動更新の切り替え。
+## v2.3 Codex Beta
+
+- Codex の使用量レコードは `sessions/**/*.jsonl` と `archived_sessions/**/*.jsonl` からだけ検出し、認証情報、DB、未知のファイルは対象外です。これとは別に、`$CODEX_HOME/session_index.jsonl` だけをストリームで読み込み、実際のスレッド名に使う `id` → `thread_name` の対応を取得します。名前に含まれる絶対パスは伏せ、名前はメモリ内だけに保持します。使用量 JSONL の各行は、許可リスト内の使用量・構造メタデータを抽出するためだけにストリーム処理で一時解析します。プロンプト、応答、コマンド、ツール引数の各フィールドは参照も分析利用もせず、保持・永続化もしません。
+- **処理済み** = 入力 + 出力、**非キャッシュ使用量** = 非キャッシュ入力 + 出力、**キャッシュ入力**は入力の部分集合、reasoning は出力の部分集合です。概要には「キャッシュ入力 / 入力」による入力キャッシュヒット率も表示します。Codex の請求コストは表示しません。先頭のサマリーカードには選択範囲の API 等価コスト推定を明示し、全期間ビューには同じ単価基準の週間推移を表示します。Claude / Codex / Compare は各プロバイダーの集計方法を分けて扱います。
+- Codex の**今日**は、設定したタイムゾーンの現在の暦日です。正確な時間別 API 等価コストを表示し、Token 構成は独立したビューとして残します。日別・月別のメインチャートも API 等価コストを既定表示にし、Token 構成は別に確認できます。正確に一致する既知モデルだけを価格計算に含めるため、未知モデルは価格未設定のままで、価格カバレッジは常に表示されます。schema 3 と互換性のある追加型の時間別 sidecar は、今日の使用を含むことが既に分かっている canonical ファイルだけを対象とし、checkpoint 保存と再開に対応します。全履歴の再インデックスは発生させません。
+- リクエスト単位の Token 帰属では、有効な `last_token_usage` の各 component を優先します。その `total_tokens` はアクティブなコンテキスト量であり、リクエスト使用量ではありません。total と last の完全な数値署名により、同じ仮名化 rate-limit ソースまたは直前の隣接レコードの再生を証明できる場合だけ除外します。last snapshot がない場合は cumulative lineage high-water にフォールバックします。アップグレード後はインデックスを一度自動再構築し、その間もインデックス済み小計を表示します。
+- Claude と Codex の全期間 / 比較ビューは、ローカル Token ログから過去の使用済み等価値を直接計算します。最新の有効な公式リセット観測を基準に、一意で重複しない週間期間の列を作り、各使用イベントは一つの期間にだけ数えます。利用できる観測がなければ、使用分のみの履歴を UTC の月曜から月曜の暦週にフォールバックします。系列名が異なっていても、重複して整合しない将来リセットは競合として扱い、二つ目の「現在の期間」を作りません。期間範囲とリセット時刻は分けて表示します。Codex の使用量は日単位のスライスで保存されます。スライスが公式の日中リセットをまたぐ場合も Token は一度だけ数え、影響する期間を「境界近似」と表示し、使用済み等価値だけを示して総上限や未使用分を逆算しません。この表示規則はインデックス schema を変更せず、再構築も発生させません。Codex の過去期間は常に使用済み値だけを表示します。最新の現在期間に限り、リセット競合がなく、インデックス済み使用量を単一の観測元へ確実に帰属できる場合だけ総上限を推定でき、現在の未使用値は結論として示しません。帰属できない複数ログインの使用量も使用済み値だけとし、架空のアカウント分割は行いません。履歴には現在の公式 API 単価を一貫して適用します。これは請求額や公式のサブスクリプション価格ではなく代理推定です。このパネルは既定で有効で、設定の `showWeeklyEquivalentValue` から非表示にできます。Claude はこのリリースからプロファイル別に上限観測を記録します。
+- Codex に切り替えても、既存の今日 / 今月 / 全期間 / セッション / プロジェクト / コンテンツ / 設定の構成を維持し、Codex の意味に合わせてラベルだけを調整します。両プロバイダーは同じ render function、HTML class、チャート、表、余白、レスポンシブルールを使います。時系列チャートの幅は揃い、密な内容だけが各領域内でスクロールします。Codex の提案はインデックス済み 30 日間の構造的根拠を使います。
+- ルートタスクには、絶対パスを伏せた最新の実際のスレッド名を使います。子タスクは自身の実際のスレッド名を優先し、ない場合は報告されたニックネームを使って親 / ルートタスク名も表示します。それらもない場合はローカライズ済みの中立的な代替名を表示します。プロジェクト名は Git リポジトリ名、Git 外ではフォルダー名です。信頼できる集計ができない Branches や Workflows は作り上げません。
+- 過去 7 日 / 30 日は、設定したタイムゾーンでイベント発生日を正確に区切ります。移行または再構築中は、Codex のカード、表、プロジェクト、セッション、提案、ステータス値を**インデックス済み小計**として明示し、未検証の旧集計値を除外します。Codex home を検出するとプロバイダータブを直ちに表示し、初回インデックス中はページ内に正確なインデックス済みファイル数、割合、容量の進捗をリアルタイム表示します。「比較」は両プロバイダーに実データが揃ってから表示します。最初のアトミックチェックポイント生成後は、インデックス処理が続いていても小計ダッシュボード全体を利用でき、進捗表示がカードや表に置き換わることはありません。選択した Codex home はアカウントを区別しないため、同じ home に複数のログインが残したログは合算されます。ローカルログには信頼できるアカウント識別子がないため、上限カードは**最終観測**のままで合算しません。
+- 各提案は、インデックス済み 30 日間の構造集計に根拠がある場合だけ、観測、読みやすい根拠、条件付きアクションを示します。根拠がなければ一般論を生成しません。
+- 永続インデックスには、マシン固有のソルトで仮名化したキー、数値・構造集計、ならびにサニタイズ済みのプロジェクト、ディレクトリ、エージェント、モデル、effort、役割、時刻、品質メタデータを保存します。生の ID、完全なパスやリポジトリ URL、スレッド名、会話本文は保存しません。
+- 共通の Settings タブは、Codex 選択時に共通設定と Codex に有効な設定だけを表示します。Codex の収集とローカル Codex 提案は別々に無効化できます。バックグラウンド監視遅延は既定 30 秒で、Off や長い間隔も選べます。初回インデックスまたは未完了の旧インデックス移行には、上限 64 GiB / 16,384 ファイルパスのストリーミング処理を 1 回与えます。この容量をメモリに事前確保せず、キャンセルと再開にも対応します。収束後はバックグラウンド処理が 128 MiB / 64 ファイルパスに戻り、常時表示される「更新」は 2 GiB / 512 ファイルパスを使います。変更のない通常更新は使用量 JSONL 本文を読みません。
+
## インストール
拡張機能ビュー(`Ctrl+Shift+X`)で **`Claude Code Usage`** を検索するか:
@@ -64,10 +78,19 @@ Cursor / Windsurf 向けに [Open VSX Registry](https://open-vsx.org/extension/G
**「No Claude Code Data」** — Claude Code がインストールされ、少なくとも一度使用されていることを確認。`dataDirectory` 設定を確認(自動検出は `~/.claude/projects` を参照)。
-**クォータが `5h:--% wk:--%`** — Claude Code に一度ログインしてください。拡張機能は `~/.claude/.credentials.json` を読み取り専用で参照します。
+**クォータが `5h:--% wk:--%`** — 現在の Claude プロファイルにログインして
+ください。認証情報は明示的な `dataDirectory`、最初の有効な
+`CLAUDE_CONFIG_DIR`、`~/.claude` の順で選ばれます。グローバルな macOS
+Keychain 項目はデフォルトプロファイルにのみ使用されます。
**古い月の履歴がない** — Claude Code は `cleanupPeriodDays`(デフォルト 30 日)より古いログを削除します。保持期間を延ばすには `~/.claude/settings.json` に `{ "cleanupPeriodDays": 365 }` を設定。削除済みのログは復元できません。
+**トークン数が Claude Code の `stats-cache` より少ない** — 1 回の応答が、
+同じ `messageId`、`requestId`、完全な `usage` ベクトルを持つ別々の
+`thinking` 行と `text` 行として転録されることがあります。本拡張機能は
+応答 ID ごとに最大ベクトルを 1 回だけ数えますが、Claude Code の
+`stats-cache` は行ごとに加算します。キャッシュ値に合わせる倍率補正は行いません。
+
**トークン数がプロバイダーのダッシュボードより少ない** — 一部のプロキシ / 動的ワークフローはエージェントごとの記録をサブディレクトリに書き込み、不完全な場合があります。実際の消費はプロバイダーの請求ページをご確認ください。ネイティブのワークフロー帰属は計画中です。
**履歴が大きい環境で CPU 使用率が高い/更新が遅い(Linux を含む)**
diff --git a/README-ko.md b/README-ko.md
index 35f44b2b..0520408a 100644
--- a/README-ko.md
+++ b/README-ko.md
@@ -4,7 +4,7 @@
---
-**Claude Code를 더 스마트하게 쓰는 방법을 알려주는 상태 표시줄 코치.** 청구 도구도, 멀티 프로바이더 모니터도 아닙니다. AI를 활용해 토큰 소비를 분석하고 사용 습관을 개선하는 가벼운 VS Code 확장입니다.
+**Claude Code와 Codex의 로컬 사용량을 개선하는 상태 표시줄 코치.** 청구 도구가 아닙니다. Claude 비용 / 쿼터 보기를 유지하면서 Codex Beta는 Codex 고유의 토큰과 동작 의미로 분석합니다.
> **무엇인가**: 로컬 Claude Code 대화 로그를 읽어 **토큰 기반**의 사용량과 비용 추정치를 보여주는 VS Code 상태 표시줄 모니터. 프롬프트 개선과 낭비 절감을 제안하는 선택적 AI 어드바이저 포함.
>
@@ -36,6 +36,20 @@
- **멀티 벤더 가격** — Opus 4.x / Sonnet 4.x / Haiku 4.5는 Anthropic 공식 가격으로 검증. OpenAI / Gemini / DeepSeek / Kimi / GLM / Qwen 참고 가격(패밀리 인식 폴백 포함). `Refresh Token Pricing`으로 LiteLLM 최신 가격 가져오기.
- **개인화** — 언어, 시간대, 소수 자릿수, 간략 숫자, 프로젝트 그룹화, 대시보드 자동 새로고침 토글.
+## v2.3 Codex Beta
+
+- Codex 사용량 레코드는 `sessions/**/*.jsonl`과 `archived_sessions/**/*.jsonl`에서만 찾으며 자격 증명, DB, 알 수 없는 파일은 제외합니다. 별도로 `$CODEX_HOME/session_index.jsonl` 하나만 정확히 스트리밍해 실제 스레드 제목에 사용할 `id` → `thread_name` 매핑을 가져옵니다. 제목 속 절대 경로는 가리고 제목은 메모리에만 유지합니다. 사용량 JSONL 각 줄은 허용 목록의 사용량·구조 메타데이터를 추출하기 위해서만 스트리밍 방식으로 일시 파싱합니다. 프롬프트, 응답, 명령, 도구 인수 필드는 검사하거나 분석에 사용하지 않으며 보관·영구 저장하지 않습니다.
+- **처리된** 값 = 입력 + 출력, **캐시되지 않은 사용량** = 캐시되지 않은 입력 + 출력, **캐시 입력**은 입력의 부분집합, reasoning은 출력의 부분집합입니다. 개요에는 캐시 입력 / 입력으로 계산한 입력 캐시 적중률도 표시합니다. Codex 청구 비용은 표시하지 않습니다. 첫 요약 카드에는 선택 범위의 API 등가 비용 추정치를 명확히 표시하고, 전체 기간 보기에는 같은 가격 기준의 주간 추이를 표시합니다. Claude / Codex / Compare는 공급자별 집계 의미를 분리합니다.
+- Codex의 **오늘**은 설정한 시간대의 현재 달력 날짜를 뜻합니다. 정확한 시간별 API 등가 비용을 표시하고 Token 구성은 별도 보기로 유지합니다. 일별·월별 기본 차트도 API 등가 비용을 기본값으로 사용하며 Token 구성은 따로 볼 수 있습니다. 정확히 일치하는 알려진 모델만 가격 계산에 포함하므로 알 수 없는 모델은 가격을 매기지 않고 가격 적용 범위는 계속 표시합니다. schema 3과 호환되는 추가형 시간별 sidecar는 오늘 사용량이 있다고 이미 확인된 canonical 파일만 처리하고 checkpoint 저장과 재개를 지원하며, 전체 기록 재인덱싱을 유발하지 않습니다.
+- 요청 단위 Token 귀속은 유효한 `last_token_usage` component를 우선 사용합니다. 그 안의 `total_tokens`는 활성 컨텍스트 크기이며 요청 사용량이 아닙니다. total과 last의 전체 숫자 서명이 동일한 가명 rate-limit 소스 또는 바로 인접한 레코드의 재생을 증명할 때만 중복을 제거합니다. last snapshot이 없으면 cumulative lineage high-water로 되돌아갑니다. 업그레이드 후 인덱스를 한 번 자동 재구축하며 그동안에도 인덱싱된 소계를 계속 표시합니다.
+- Claude와 Codex의 전체 기간 / 비교 보기는 로컬 Token 로그에서 과거 사용 등가치를 직접 계산합니다. 가장 최근의 유효한 공식 재설정 관측 하나만 고유하고 겹치지 않는 주간 기간열의 기준이 되므로 각 사용 이벤트는 한 기간에만 집계됩니다. 사용할 수 있는 관측이 없으면 사용분 전용 기록만 UTC 월요일부터 월요일까지의 달력 주로 되돌아갑니다. series 이름이 달라도 겹치면서 정렬되지 않은 미래 재설정은 충돌로 처리하며 두 번째 「현재 기간」을 만들 수 없습니다. 기간 범위와 재설정 시각은 따로 표시합니다. Codex 사용량은 일별 슬라이스로 저장됩니다. 슬라이스가 공식 일중 재설정을 가로지르더라도 Token은 한 번만 집계하고, 영향을 받은 기간을 「경계 근사치」로 표시하며 사용된 등가치만 보여 주고 총한도나 미사용분을 역산하지 않습니다. 이 표시 규칙은 인덱스 schema를 변경하거나 재구축을 유발하지 않습니다. Codex 과거 기간은 항상 사용된 값만 표시합니다. 최신 현재 기간에 한해 재설정 충돌이 없고 인덱싱된 사용량을 단일 관측 출처에 안정적으로 귀속할 수 있을 때만 총한도를 추정할 수 있으며, 현재 미사용 값은 결론으로 제시하지 않습니다. 귀속할 수 없는 여러 로그인 사용량도 사용된 값만 표시하며 계정 분리를 만들어 내지 않습니다. 현재 공식 API 단가를 전체 기록에 일관되게 적용합니다. 이는 청구액이나 공식 구독 가격이 아닌 대리 추정치입니다. 이 패널은 기본으로 켜지며 설정의 `showWeeklyEquivalentValue`에서 끌 수 있습니다. Claude는 이 릴리스부터 프로필별 한도 관측을 기록합니다.
+- Codex로 전환해도 기존 오늘 / 이번 달 / 전체 기간 / 세션 / 프로젝트 / 콘텐츠 / 설정 구조를 유지하고 Codex 의미에 맞게 레이블만 조정합니다. 두 공급자는 같은 render function, HTML class, 차트, 표, 간격 및 반응형 규칙을 사용합니다. 시계열 차트는 너비가 정렬되고 밀집된 콘텐츠만 각 영역 안에서 스크롤됩니다. Codex 제안은 인덱싱된 30일 구조 근거를 사용합니다.
+- 루트 작업은 절대 경로를 가린 최신 실제 스레드 제목을 사용합니다. 자식 작업은 자신의 실제 스레드 제목을 우선 사용하고, 제목이 없으면 보고된 닉네임을 사용하면서 부모 / 루트 작업 제목도 표시합니다. 이 정보도 없으면 현지화된 중립 대체 이름을 표시합니다. 프로젝트 이름은 Git 저장소 이름을, Git 외부에서는 폴더 이름을 사용합니다. 안정적으로 집계할 수 없는 Branches나 Workflows를 만들어 내지 않습니다.
+- 최근 7일 / 30일 값은 설정한 시간대에서 이벤트 발생일을 정확히 나눠 계산합니다. 마이그레이션이나 재구축이 진행 중이면 Codex 카드, 표, 프로젝트, 세션, 권장 사항 및 상태 값을 **인덱싱된 소계**로 명시하고 검증되지 않은 이전 합계를 제외합니다. Codex home이 감지되면 공급자 탭을 즉시 표시하고, 최초 인덱싱 중에는 페이지 안에 정확한 인덱싱 파일 수, 백분율, 용량 진행 상황을 실시간으로 표시합니다. 비교 탭은 두 공급자 모두 실제 데이터가 생긴 뒤에만 표시됩니다. 첫 원자적 체크포인트가 생성되면 인덱싱이 계속되는 동안에도 전체 소계 대시보드를 사용할 수 있으며 진행 표시가 카드나 표를 대체하지 않습니다. 선택한 Codex home은 계정을 구분하지 않으므로 같은 home에 여러 로그인이 남긴 로그는 합산됩니다. 로컬 로그에는 신뢰할 수 있는 계정 식별자가 없으므로 한도 카드는 **마지막 관측**만 유지하며 합산하지 않습니다.
+- 각 제안은 인덱싱된 30일 구조 집계에 근거가 있을 때만 관측, 읽기 쉬운 근거, 조건부 행동을 보여 줍니다. 근거가 없으면 일반적인 조언을 생성하지 않습니다.
+- 영구 인덱스에는 기기별 솔트로 가명화한 키, 수치·구조 집계, 그리고 정제된 프로젝트, 디렉터리, 에이전트, 모델, effort, 역할, 시간, 품질 메타데이터를 저장합니다. 원시 ID, 전체 경로나 저장소 URL, 스레드 제목, 대화 본문은 저장하지 않습니다.
+- 공용 Settings 탭은 Codex 선택 시 공통 설정과 Codex에 유효한 설정만 표시합니다. Codex 수집과 로컬 Codex 제안은 각각 끌 수 있습니다. 백그라운드 감시 지연은 기본 30초이며 Off 또는 더 긴 간격도 선택할 수 있습니다. 최초 인덱스 또는 완료되지 않은 이전 인덱스 마이그레이션에는 한 번의 제한된 64 GiB / 16,384 파일 패스 스트리밍 상한을 적용합니다. 이 용량만큼 메모리를 미리 할당하지 않으며 취소와 재개를 지원합니다. 수렴 후 백그라운드 작업은 128 MiB / 64 파일 패스로 돌아가고, 항상 보이는 새로 고침은 2 GiB / 512 파일 패스를 사용합니다. 변경 없는 일반 새로 고침은 사용량 JSONL 본문을 읽지 않습니다.
+
## 설치
확장 보기(`Ctrl+Shift+X`)에서 **`Claude Code Usage`**를 검색하거나:
@@ -64,10 +78,19 @@ Cursor / Windsurf용으로 [Open VSX Registry](https://open-vsx.org/extension/Gr
**"No Claude Code Data"** — Claude Code가 설치되어 최소 한 번 사용되었는지 확인하고, `dataDirectory` 설정을 확인하세요(자동 감지는 `~/.claude/projects`를 봅니다).
-**쿼터가 `5h:--% wk:--%`로 표시** — Claude Code에 한 번 로그인하세요. 확장은 `~/.claude/.credentials.json`을 읽기 전용으로 참조합니다.
+**쿼터가 `5h:--% wk:--%`로 표시** — 현재 Claude 프로필에 로그인하세요.
+자격 증명은 명시적 `dataDirectory`, 첫 번째 유효한 `CLAUDE_CONFIG_DIR`,
+`~/.claude` 순서로 선택됩니다. 전역 macOS Keychain 항목은 기본 프로필에만
+사용됩니다.
**이전 달 기록 누락** — Claude Code는 `cleanupPeriodDays`(기본 30일)보다 오래된 로그를 삭제합니다. 더 오래 보관하려면 `~/.claude/settings.json`에 `{ "cleanupPeriodDays": 365 }`를 설정하세요. 이미 삭제된 로그는 복구할 수 없습니다.
+**토큰 수가 Claude Code의 `stats-cache`보다 적음** — 한 응답이 동일한
+`messageId`, `requestId`, 전체 `usage` 벡터를 가진 별도의 `thinking` 행과
+`text` 행으로 기록될 수 있습니다. 확장은 응답 ID별로 가장 큰 벡터를 한 번만
+계산하지만 Claude Code의 `stats-cache`는 행별로 더합니다. 캐시 값에 맞추기
+위한 배율 보정은 적용하지 않습니다.
+
**토큰 수가 프로바이더 대시보드보다 적음** — 일부 프록시 / 동적 워크플로는 에이전트별 기록을 하위 디렉터리에 쓰며 불완전할 수 있습니다. 실제 지출은 프로바이더 청구 페이지를 확인하세요. 네이티브 워크플로 귀속 기능은 계획 중입니다.
**큰 기록에서 CPU 사용률이 높거나 새로 고침이 느림(Linux 포함)**
diff --git a/README-zh-CN.md b/README-zh-CN.md
index 1c4ef0f0..04a341a4 100644
--- a/README-zh-CN.md
+++ b/README-zh-CN.md
@@ -4,7 +4,7 @@
---
-**状态栏中的 Claude Code 使用教练。** 它不是账单工具,也不是多供应商监控面板,而是一个轻量级 VS Code 插件,专注于精确归因 token 消耗,并借助 AI 帮你更聪明地使用 Claude Code。
+**状态栏中的 Claude Code 与 Codex 本地用量教练。** 它不是账单工具。Claude 保留成本与配额视图;Codex Beta 按自己的 token 与行为语义提供分析,无需硬套 Claude 的结构。
> **它是什么**:一个 VS Code 状态栏小工具,读取本地 Claude Code 对话日志,用 token 数量乘以公开单价来估算使用量和成本;并可提供 AI 建议,帮你优化提示、减少浪费。
>
@@ -26,7 +26,8 @@

-*来自真实 `/usage` 数据:利用率百分比、重置倒计时,以及每周重置的星期和时间。*
+*来自真实 `/usage` 数据:利用率百分比,以及每个窗口的剩余时间与重置时刻。*
+*你的方案计量的每一项每周上限都会各占一行,按模型划分的上限也在其中(名称由 Anthropic 提供,因此该行会跟随实际受限的模型);若你启用了使用额度,也会一并显示。*
### 仪表板
@@ -60,6 +61,20 @@ AI 建议生成的是一份 **Markdown 文档**,用文字展示比截图更直
---
+## 2.3 Codex Beta
+
+- Codex 用量记录只会从 `sessions/**/*.jsonl` 与 `archived_sessions/**/*.jsonl` 中发现;凭据、数据库和未知文件仍明确排除。此外,插件会精确流式读取 `$CODEX_HOME/session_index.jsonl`,仅将 `id` 映射为 `thread_name`,用于真实线程标题。标题中的绝对路径会先脱敏,标题只保留在内存。用量 JSONL 会逐行流式、临时解析,以提取白名单内的用量与结构元数据;提示词、回复、命令和工具参数字段不会被检查或用于分析,也不会被保留或持久化。
+- **已处理** = 输入 + 输出;**未缓存用量** = 未缓存输入 + 输出;**缓存输入**仍是输入的子集,reasoning 仍是输出的子集。概览还会按「缓存输入 / 输入」显示输入缓存命中率。Codex 不显示账单成本;首张汇总卡会针对当前范围显示明确标注的 API 等效成本估算,「全部时间」则按同一定价口径展示每周趋势。Claude / Codex / Compare 仍保持各供应商口径独立。
+- Codex 的**今天**指配置时区中的当前自然日。页面会显示精确的小时 API 等效成本,并把 Token 构成作为独立视图;每日与每月主图也默认显示 API 等效成本,Token 构成仍单独保留。只有精确匹配的已知模型才参与计价,未知模型保持未定价,定价覆盖率始终可见。这个兼容 schema 3 的增量小时 sidecar 仅处理已知包含今天用量的 canonical 文件,支持检查点与续传,且不会触发全历史重建。
+- 单次请求 Token 归因优先采用有效的 `last_token_usage` component;其中 `total_tokens` 表示活跃上下文大小,不是该请求的用量。只有完整的 total-plus-last 数字签名能证明同一伪名 rate-limit 来源或紧邻记录发生重放时才去重;缺少 last snapshot 时回退为 cumulative lineage high-water。升级后会自动重建一次索引,期间持续显示已索引小计。
+- Claude 与 Codex 的「全部时间」/「对比」会直接根据本地 Token 日志计算历史已用等价值。最新且有效的官方重置观测只会锚定一组唯一、互不重叠的每周周期,每条用量只计入一个周期;没有可用观测时,仅已用历史才回退到 UTC 周一至周一的自然周。任何重叠但未对齐的未来重置都会视为冲突,即使 series 名称不同,也不能再生成第二个「当前周期」;周期范围与重置时刻会分开表达。Codex 用量按日切片保存:若某个切片跨过官方日内重置,其 Token 仍只计一次,受影响周期会标为「边界近似」,并且只显示已用等价值,不反推总额度或未用额度。该显示规则不改变索引 schema,也不会触发重建。Codex 历史周期一律仅显示已用值;只有最新当前周期在重置无冲突、且已索引用量可可靠归属到单一观测来源时,才允许反推总额度,当前未用值仍不会作为结论。无法归属的多登录用量同样只显示已用值,不会虚构账号拆分。历史统一套用当前官方 API 单价;这是代理估算,不是账单或官方订阅价格。该面板默认开启,可在设置中通过 `showWeeklyEquivalentValue` 关闭。Claude 从此版本开始按本地 profile 记录额度观测。
+- 切换到 Codex 后仍沿用既有的今天 / 本月 / 全部时间 / 会话 / 项目 / 内容 / 设置结构,并按 Codex 语义调整标签。两个供应商复用同一组渲染函数、HTML class、图表、表格、间距和响应式规则;时间序列图保持宽度对齐,密集内容只在各自区域内滚动。Codex 建议使用已索引的 30 天结构化证据。
+- 根任务使用经过路径脱敏的最新真实线程标题;子任务优先使用自己的真实线程标题,缺失时使用其上报的昵称并显示父级 / 根任务标题;这些信息仍缺失时采用本地化的中性回退名称。项目名称使用 Git 仓库名,非 Git 目录则使用文件夹的末级名称。插件不会虚构无法可靠统计的 Branches 或 Workflows。
+- 最近 7 天与 30 天按配置时区中的事件发生日精确切片。迁移或重建未完成时,Codex 的卡片、表格、项目、会话、建议与状态栏都会明确标为**已索引小计**,并排除未经验证的旧版总量。检测到 Codex home 后会立即显示供应商标签;首次建立索引期间在页面内实时显示精确的已索引文件数、百分比与容量进度,「对比」则等到两个供应商都有真实数据后才出现。生成首个原子检查点后,完整的小计仪表盘会在索引继续期间保持可用;进度提示不会替代卡片和表格。所选 Codex home 不区分账号,因此同一目录中多个登录账号留下的日志会合并统计;本地日志没有可靠的账号身份字段,额度卡只能保留**最后观测**,不会相加。
+- 每条建议仅在已索引的 30 天结构聚合有证据时展示观测、易读证据和条件式行动;没有证据就不会生成泛化建议。
+- 持久索引保存使用本机盐值生成的假名化键、数值与结构聚合,以及经过脱敏的项目、目录、agent、模型、effort、角色、时间和质量元数据;绝不保存原始 ID、完整路径或仓库 URL、线程标题或对话正文。
+- 共享设置标签页在 Codex 下只显示通用和对 Codex 有效的选项。Codex 数据收集和本地 Codex 建议可分别关闭;后台监听延迟可配置(默认 30 秒,也可关闭或选更长间隔)。首次建立索引或旧索引迁移尚未收敛时,会获得一次受限的 64 GiB / 16,384 文件轮次流式上限;这不会预先占用等量内存,并且仍可取消、可续传。收敛后,后台任务恢复为 128 MiB / 64 文件轮次,始终可见的「刷新」使用 2 GiB / 512 文件轮次。未变化的常规刷新仍不会读取用量 JSONL 正文。
+
## 2.2 新功能
- **用量分享卡**(可选,`enableShareCard`):一张可配置的单页 SVG 用量卡——自选时间范围 × 范围(总体 / 工程 / 会话)× 展示哪些指标,以及主题(**Claude 经典橙** / **奶油** / **极光暗色** / **自动**),可选带上 GitHub 头像与名称。自包含、可复现;提示词、路径、ID 一律不出本机。中文语言下使用 万/亿 单位。
@@ -85,14 +100,17 @@ AI 建议生成的是一份 **Markdown 文档**,用文字展示比截图更直
- **工作流配额护栏**:当 5 小时窗口剩余不足以完成一次运行时,仪表板显示可关闭的警告横幅(`claudeCodeUsage.workflowQuotaWarnPercent`)。
- **设置搬进仪表板**:新增 ⚙ 设置标签页,就地管理所有选项;VS Code 原生设置只保留三个适合同步的(`language`、`dataDirectory`、`advice.apiKey`)。右上角按钮精简为 ✨ AI 建议 和 ⚙ 设置(都跳到对应标签);自动刷新开关挪进设置(暂停时右上角才出现手动 ↻)。如果你把成本、配额、上下文三项**全部隐藏**,状态栏会保留一个小图标作为回到仪表板的入口。
- **状态栏指标**(`statusBarMetric`):默认显示今日成本,也可切换为今日**总 token** 消耗(紧凑 k/M)。
-- **每周 Opus 上限**(`showOpusWeekly`,可选开启):在配额项后追加 `opus:NN%`,方便重度 Opus 用户一眼看到每周 Opus 额度。(PR #38,[@wheelbarrel00](https://github.com/wheelbarrel00)。)
+- **模型专属每周上限**(`showScopedWeekly`,可选开启):显示 Anthropic
+ 实际返回的受限模型名称,例如 `fable 17%`;由 PR #38
+ ([@wheelbarrel00](https://github.com/wheelbarrel00))最初的
+ 模型专属额度功能演进而来。
- **AI 建议 2.0**:自备 key:默认 **Anthropic**(`/v1/messages`),也支持任意 OpenAI 兼容端点(`advice.apiFormat`)。引入新信号(运行、缓存命中率、归因、思考占比);可选 `advice.userContext` 会附上"针对本项目的个性化"一节;`advice.promptWindowDays`(默认 30)设定采样窗口。传输层加强了:超时、重试、curl 兜底。*(一个免 key 的"订阅"后端做过原型,但本版未上线 —— Anthropic 封禁用 Claude Code 的 OAuth token 直连 API;若日后放开会再启用。)*
- **用量优化器**(实验性,`advice.optimizer.enabled`,默认关闭):Content 标签页的一张卡,粘贴进粗略需求,返回一条精炼提示词,以**纯文本**形式(可直接粘贴、无 Markdown),并附推荐的 effort / thinking / 模型。三个可选微调项(标出含糊指代 · 压缩长粘贴内容 · 建议风格方向)。**只发送你粘贴的文字**,且首次有一次性同意确认。
- **上下文窗口指示器**(实验性,默认关闭):在设置里开启后,状态栏显示当前 session 的上下文占用。`~` 表示窗口大小是猜测;代理 / 自定义模型可用 `contextWindowOverride` 手填真实窗口。
## 2.0 新功能
-- **真实的 5 小时和每周配额** 显示在状态栏:读取 Claude Code 现有的 OAuth 会话(`~/.claude/.credentials.json` 或 macOS 钥匙串),无需配置。借鉴上游 [PR #9](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/9)([@Dobidop](https://github.com/Dobidop))。
+- **真实的 5 小时和每周配额** 显示在状态栏:OAuth 会话跟随当前窗口所选的 Claude profile,优先级为显式 `dataDirectory`、首个有效 `CLAUDE_CONFIG_DIR`、最后 `~/.claude`;macOS 钥匙串仅用于默认 profile。借鉴上游 [PR #9](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/9)([@Dobidop](https://github.com/Dobidop))。
- **四个新标签页**:Sessions、Projects、Content、Branches,均可排序。
- **堆叠 token 构成图**,含 Y 轴和参考线。
- **AI 建议命令**(默认 DeepSeek V4 Pro,`reasoning_effort=max`),未配置 key 时提供 demo 演示。
@@ -175,7 +193,7 @@ ext install GrowthJack.claude-code-usage
- 检查 `dataDirectory` 设置;自动检测会查 `~/.claude/projects` 和 `~/.config/claude/projects`。
**配额行显示 `5h:--% wk:--%`**
-- Claude Code 的 OAuth token 缺失或过期。登录一次 Claude Code;扩展会读取 `~/.claude/.credentials.json`(或 macOS 钥匙串里 Claude Code 用的条目)并在需要时刷新 bearer。
+- Claude Code 的 OAuth token 缺失或过期。请登录一次当前 Claude profile。凭证按显式 `dataDirectory`、首个有效 `CLAUDE_CONFIG_DIR`、最后 `~/.claude` 的顺序选择;全局 macOS 钥匙串条目不会替代已选中的自定义 profile。
**切换 workspace 后配额消失**
- 已修复:在同一个窗口里切换打开的文件夹时,配额会自动重新拉取,无需新开窗口。
@@ -202,6 +220,12 @@ ext install GrowthJack.claude-code-usage
```
此设置仅对之后生成的日志有效。感谢 [@nickearnshaw](https://github.com/nickearnshaw) 记录此项。
+**Token 总数低于 Claude Code 的 `stats-cache`**
+- 一次响应会在转录中产生 `thinking` 和 `text` 两条记录;它们带有相同的
+ `messageId`、`requestId` 与完整 `usage` 向量。插件按响应身份只计一次,
+ 并保留该身份最大的向量;Claude Code 的 `stats-cache` 则按行累加。
+ 因此两者可能不同,插件不会用倍率去贴合缓存读数。
+
---
## 致谢
@@ -215,7 +239,7 @@ ext install GrowthJack.claude-code-usage
- [@Dobidop](https://github.com/Dobidop) —— [PR #9](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/9),读取真实 `/usage` 数据的 OAuth 方案;配额指示器据此改编。
- [@nickearnshaw](https://github.com/nickearnshaw) —— [PR #8](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/8) 本地化数字 / 日期;[PR #20](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/20) 修复 webview / 状态栏卡在 "Loading…";[PR #21](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/21) `cleanupPeriodDays` 文档;[PR #24](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/24) 配额窗口滚动处理。
- [@ScherbakovAl](https://github.com/ScherbakovAl) —— [PR #31](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/31),状态栏上下文窗口指示器与 `showCost` 开关的原始实现。
-- [@wheelbarrel00](https://github.com/wheelbarrel00) —— [PR #38](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/38),状态栏可选显示每周 Opus 上限(`showOpusWeekly`)。
+- [@wheelbarrel00](https://github.com/wheelbarrel00) —— [PR #38](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/38),状态栏可选显示每周 Opus 上限,即如今由 API 动态命名的 `showScopedWeekly` 前身。
- [@brenoneill](https://github.com/brenoneill) —— [PR #14](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/14),自定义数据目录(已并入上游 1.0.8)。
- [@mxzinke](https://github.com/mxzinke) —— Opus 4.5 / Haiku 4.5 价格 + 德语翻译(上游 1.0.8)。
diff --git a/README-zh-TW.md b/README-zh-TW.md
index 4b61cb1c..222d1979 100644
--- a/README-zh-TW.md
+++ b/README-zh-TW.md
@@ -4,7 +4,7 @@
---
-**看清你的 Claude Code 用量,讓 AI 幫你用得更好。** 不是帳單工具,不是多 provider 監控面板。一個專注於 token 精確歸因、並以 AI 幫你優化使用習慣的輕量 VS Code 插件。
+**看清 Claude Code 與 Codex 的本地用量,讓 AI 幫你用得更好。** 不是帳單工具。Claude 保留成本與配額檢視;Codex Beta 依自己的 token 與行為語意提供分析。
> **它是什麼**:一個 VS Code 狀態列小工具,讀取本地 Claude Code 對話日誌,按 token × 公開單價估算用量與成本;並提供可選的 AI 建議功能,幫你優化提示詞、減少不必要的 token 消耗。
>
@@ -36,6 +36,20 @@
- **多廠商定價** — Opus 4.x / Sonnet 4.x / Haiku 4.5 對照 Anthropic 官方定價;OpenAI / Gemini / DeepSeek / Kimi / GLM / Qwen 參考價,含家族感知回退。`Refresh Token Pricing` 可拉取 LiteLLM 即時價格。
- **個人化** — 語言、時區、小數位數、精簡數字、專案分組、儀表板自動刷新開關。
+## v2.3 Codex Beta
+
+- Codex 用量記錄只會從 `sessions/**/*.jsonl` 與 `archived_sessions/**/*.jsonl` 中發現;憑證、資料庫與未知檔案仍明確排除。此外,擴充功能會精確串流讀取 `$CODEX_HOME/session_index.jsonl`,僅將 `id` 對應到 `thread_name`,用於真實執行緒標題。標題中的絕對路徑會先去識別化,標題只保留在記憶體。用量 JSONL 會逐行串流並暫時解析,以擷取白名單內的用量與結構中繼資料;提示、回覆、命令和工具參數欄位不會被檢查或用於分析,也不會被保留或持久化。
+- **已處理** = 輸入 + 輸出;**未快取用量** = 未快取輸入 + 輸出;**快取輸入**仍是輸入的子集,reasoning 仍是輸出的子集。概覽也會依「快取輸入 / 輸入」顯示輸入快取命中率。Codex 不顯示帳單成本;首張摘要卡會針對目前範圍顯示明確標示的 API 等效成本估算,「全部時間」則依同一定價口徑顯示每週趨勢。Claude / Codex / Compare 仍維持各供應商口徑獨立。
+- Codex 的**今天**指設定時區中的目前自然日。頁面會顯示精確的每小時 API 等效成本,並將 Token 構成保留為獨立檢視;每日與每月主圖也預設顯示 API 等效成本,Token 構成仍分開呈現。只有精確符合的已知模型才參與計價,未知模型維持未定價,定價涵蓋率始終可見。這個相容 schema 3 的增量小時 sidecar 只處理已知包含今天用量的 canonical 檔案,支援檢查點與續傳,且不會觸發完整歷史重建。
+- 單次請求 Token 歸因優先採用有效的 `last_token_usage` component;其中 `total_tokens` 表示目前上下文大小,不是該次請求的用量。只有完整的 total-plus-last 數字簽名能證明同一假名化 rate-limit 來源或相鄰記錄發生重播時才去重;缺少 last snapshot 時回退為 cumulative lineage high-water。升級後會自動重建一次索引,期間持續顯示已索引小計。
+- Claude 與 Codex 的「全部時間」/「比較」會直接依本機 Token 日誌計算歷史已用等價值。最新且有效的官方重設觀測只會錨定一組唯一、互不重疊的每週週期,每筆用量只計入一個週期;沒有可用觀測時,僅已用歷史才回退到 UTC 週一至週一的自然週。任何重疊但未對齊的未來重設都會視為衝突,即使 series 名稱不同,也不能再建立第二個「目前週期」;週期範圍與重設時刻會分開呈現。Codex 用量按日切片保存:若某個切片跨過官方日內重設,其 Token 仍只計一次,受影響週期會標為「邊界近似」,並且只顯示已用等價值,不反推總額度或未用額度。此顯示規則不改變索引 schema,也不會觸發重建。Codex 歷史週期一律只顯示已用值;只有最新目前週期在重設無衝突,且已索引用量可可靠歸屬到單一觀測來源時,才允許反推總額度,目前未用值仍不作為結論。無法歸屬的多登入用量同樣只顯示已用值,不會虛構帳號拆分。歷史統一套用目前官方 API 單價;這是代理估算,不是帳單或官方訂閱價格。此面板預設開啟,可在設定中透過 `showWeeklyEquivalentValue` 關閉。Claude 從此版本開始依本機 profile 記錄額度觀測。
+- 切換到 Codex 後仍沿用既有的今天 / 本月 / 全部時間 / 工作階段 / 專案 / 內容 / 設定結構,並依 Codex 語義調整標籤。兩個供應商共用同一組 render function、HTML class、圖表、表格、間距與響應式規則;時間序列圖維持寬度對齊,密集內容只在各自區域內捲動。Codex 建議使用已索引的 30 天結構化證據。
+- 根任務使用經過路徑去識別化的最新真實執行緒標題;子任務優先使用自己的真實執行緒標題,缺少時使用其回報的暱稱並顯示父級 / 根任務標題;這些資訊仍缺少時採用本地化的中性回退名稱。專案名稱使用 Git 儲存庫名稱,非 Git 目錄則使用資料夾的末級名稱。擴充功能不會虛構無法可靠統計的 Branches 或 Workflows。
+- 最近 7 天與 30 天依設定時區中的事件發生日精確切片。遷移或重建未完成時,Codex 的卡片、表格、專案、工作階段、建議與狀態列都會明確標為**已索引小計**,並排除未驗證的舊版總量。偵測到 Codex home 後會立即顯示供應商分頁;首次建立索引期間會在頁面內即時顯示精確的已索引檔案數、百分比與容量進度,「比較」則等到兩個供應商都有真實資料後才出現。產生第一個原子檢查點後,完整的小計儀表盤會在索引繼續期間保持可用;進度提示不會取代卡片和表格。所選 Codex home 不區分帳戶,因此同一目錄中多個登入帳戶留下的記錄會合併統計;本機記錄沒有可靠的帳戶身分欄位,額度卡只能保留**最後觀測**,不會相加。
+- 每則建議只在已索引的 30 天結構聚合有證據時顯示觀測、易讀證據和條件式行動;沒有證據就不會產生泛化建議。
+- 持久索引保存使用本機鹽值產生的假名化鍵、數值與結構聚合,以及經過去識別化的專案、目錄、agent、模型、effort、角色、時間和品質中繼資料;絕不保存原始 ID、完整路徑或儲存庫 URL、執行緒標題或對話正文。
+- 共用設定分頁在 Codex 下只顯示通用與對 Codex 有效的選項。Codex 資料收集和本地 Codex 建議可分別關閉;背景監聽延遲可設定(預設 30 秒,也可關閉或選更長間隔)。首次建立索引或舊索引遷移尚未收斂時,會取得一次受限的 64 GiB / 16,384 檔案輪次串流上限;這不會預先占用等量記憶體,且仍可取消、可續傳。收斂後,背景工作恢復為 128 MiB / 64 檔案輪次,永遠可見的「重新整理」使用 2 GiB / 512 檔案輪次。未變更的常規重新整理仍不會讀取用量 JSONL 內文。
+
## 安裝
在擴充功能檢視(`Ctrl+Shift+X`)搜尋 **`Claude Code Usage`**,或:
@@ -64,10 +78,17 @@ ext install GrowthJack.claude-code-usage
**「無 Claude Code 資料」** — 確認 Claude Code 已安裝並至少使用過一次;檢查 `dataDirectory` 設定(自動偵測會查 `~/.claude/projects`)。
-**配額顯示 `5h:--% wk:--%`** — 登入 Claude Code 一次即可;插件以唯讀方式讀取 `~/.claude/.credentials.json`。
+**配額顯示 `5h:--% wk:--%`** — 請登入目前的 Claude profile。憑證依序跟隨
+明確設定的 `dataDirectory`、第一個有效的 `CLAUDE_CONFIG_DIR`、最後
+`~/.claude`;全域 macOS 鑰匙圈項目只用於預設 profile。
**缺少較早月份的歷史** — Claude Code 會刪除超過 `cleanupPeriodDays`(預設 30 天)的日誌。若要保留更多,在 `~/.claude/settings.json` 設定 `{ "cleanupPeriodDays": 365 }`。已刪除的日誌無法復原。
+**Token 總數低於 Claude Code 的 `stats-cache`** — 一次回應可在轉錄中產生
+分開的 `thinking` 與 `text` 記錄,兩者帶有相同的 `messageId`、`requestId`
+和完整 `usage` 向量。擴充功能按回應身分只計一次並保留最大向量;Claude
+Code 的 `stats-cache` 則逐行累加。本擴充功能不會用倍率去貼合快取讀數。
+
**Token 數低於供應商後台** — 部分代理 / 動態工作流會將各 agent 記錄寫入子目錄,可能不完整。實際消費請以供應商帳單為準。原生工作流歸因功能規劃中。
**大量歷史資料下 CPU 佔用高或重新整理緩慢(包括 Linux)**
diff --git a/README.md b/README.md
index 707220ee..b8c0594c 100644
--- a/README.md
+++ b/README.md
@@ -4,26 +4,32 @@
[](https://marketplace.cursorapi.com/items/?itemName=GrowthJack.claude-code-usage)
[](https://opensource.org/licenses/MIT)
-**The Claude Code coach in your status bar.** Not a billing tool. Not a
-multi-provider monitor. A focused token tracker that uses AI to help you
-use Claude Code better.
+**The local Claude Code and Codex usage coach in your status bar.** Not a
+billing tool. Claude keeps its cost and quota views; the v2.3 Codex Beta adds
+provider-specific token and behaviour insights through the same dashboard tabs,
+render functions, and visual system without pretending both providers expose
+the same data.
> **What this is:** A VS Code status-bar monitor that reads your local
-> Claude Code conversation logs and shows **token-derived** usage and cost
-> estimates — plus an optional AI advisor that suggests how to improve
-> your prompts and reduce waste.
+> Claude Code and Codex logs and shows provider-appropriate usage views — plus
+> optional guidance that helps reduce avoidable token and workflow overhead.
>
-> **What this is _not_:** a billing tool. All amounts are estimates based
-> on public per-million-token rates. Refer to your Anthropic account for
-> actual charges.
+> **What this is _not_:** a billing tool. Claude dollar amounts are estimates
+> based on public per-million-token rates. Codex has no billing-cost card; its
+> first summary card is an explicitly labelled API-equivalent cost estimate,
+> and its weekly allowance panel uses the same proxy rather than billing data.
+> Refer to the provider account for billing truth.
-> **看清你的 Claude Code 用量,让 AI 帮你用得更好。**
+> **看清 Claude Code 与 Codex 的本地用量,让 AI 帮你用得更好。**
>
-> **简介**:一个 VS Code 状态栏小工具,读取本地 Claude Code 对话日志,
-> 按 token × 公开单价估算用量与成本;并提供可选的 AI 建议功能,帮你优化
-> 提示词、减少不必要的 token 消耗。
+> **简介**:一个 VS Code 状态栏小工具。Claude 保留成本与配额视图;
+> v2.3 的 Codex Beta 则按 Codex 自身的数据语义展示 token、effort、任务结构
+> 和本地优化建议,同时复用 Claude 仪表盘的标签页、渲染函数和视觉体系,
+> 而不是另做一套页面或强行套用 Claude 的统计口径。
>
-> **它不是什么**:账单工具。显示金额均为估算值,实际费用请以官方账单为准。
+> **它不是什么**:账单工具。Claude 金额为估算值;Codex 首张汇总卡仅显示
+> 明确标注的 API 等效成本估算,每周额度面板也使用同一代理口径,而非账单数据。
+> 实际费用请以相应供应商的官方账单为准。
🌐 **Multi-language documentation**:
[English](README-en.md) ·
@@ -47,8 +53,8 @@ Hover the quota indicator for a breakdown:

-*Real `/usage` data — utilisation percent, reset countdown, and the
-weekly reset weekday and time.*
+*Real `/usage` data: utilisation percent, plus time left and the wall-clock reset for every window.*
+*Every weekly cap your plan meters gets its own row, per-model ones included (Anthropic supplies the name, so the row follows whichever model is capped), plus usage credits when you have them enabled.*
### Dashboard
@@ -100,6 +106,104 @@ consent prompt.
---
+## What's new in 2.3
+
+- **Codex Beta, enabled by default** — usage records are discovered only from
+ `sessions/**/*.jsonl` and `archived_sessions/**/*.jsonl`; credential,
+ database, and unknown files stay excluded. Separately, the extension streams
+ exactly `$CODEX_HOME/session_index.jsonl` to map `id` to `thread_name` for
+ truthful thread titles. Absolute paths are redacted and titles stay memory-only.
+ Usage-record JSONL lines are streamed and temporarily parsed only to extract
+ allowlisted usage and structural metadata; prompt, response, command, and
+ tool-argument fields are not inspected or used for analysis, and are never
+ retained or persisted.
+ Disable Codex at any time in **Settings → Providers**.
+- **Codex-native metrics** — **processed** = input + output; **uncached usage** =
+ uncached input + output; **cached input** is a subset of input; **reasoning**
+ is a subset of output. The overview also shows input cache hit rate as cached
+ input / input. No Codex billing cost is shown. The first Codex summary card is
+ a clearly labelled API-equivalent cost estimate for the selected scope; the
+ All-time view also shows the weekly trend using the same pricing basis.
+- **Calendar-day Today and cost trends** — Codex Today means the current day in
+ your configured timezone and adds exact hourly API-equivalent cost beside a
+ separate token-composition view. Daily and monthly primary charts default to
+ API-equivalent cost while token composition stays separately visible. Only
+ exact known-model prices contribute; unknown models remain unpriced and every
+ row keeps pricing coverage visible. The schema-3-compatible hourly sidecar
+ processes only canonical files already known to contain today, is checkpointed
+ and resumable, and does not force a full-history reindex.
+- **Request-level token attribution** — valid `last_token_usage` components are
+ preferred, while its `total_tokens` remains an active-context measurement,
+ not request usage. A full numeric total-plus-last signature suppresses only
+ proven replay; missing last snapshots fall back to cumulative lineage
+ high-water. Upgrading triggers one automatic reindex, with the indexed
+ subtotal still visible throughout the pass.
+- **Weekly allowance-value trend** — Claude and Codex All-time / Compare views
+ calculate historical used equivalents directly from local token logs. The
+ newest valid official reset observation anchors one sequence of unique,
+ non-overlapping weekly periods, and each usage event belongs to exactly one
+ period; without a usable observation, usage-only rows fall back to
+ Monday-to-Monday UTC calendar weeks. Any overlapping, non-aligned future reset
+ is a conflict even if its series name differs; it cannot create a second current
+ period, and period ranges are shown separately from reset times. Codex usage is
+ stored in daily slices: when one crosses an official intraday reset, its tokens
+ are still counted once, the affected period is labelled a boundary
+ approximation, and only used equivalent is shown. This display rule neither
+ changes the index schema nor triggers a rebuild. Codex historical periods are
+ always used-value-only. Only the newest current period may infer a total when
+ the reset is unambiguous and indexed usage can be attributed to one observation
+ source; current unused value is still withheld. Multi-sign-in usage that cannot
+ be attributed reliably also remains used-only, without inventing an account
+ split. Current official API rates are applied consistently across history. This
+ is a proxy, not a bill or an official subscription price. The panel is enabled
+ by default and can be hidden in Settings with `showWeeklyEquivalentValue`.
+- **One dashboard render stack** — switching to Codex keeps the established
+ Today / Month / All time / Sessions / Projects / Content / Settings structure,
+ relabelled where Codex semantics differ. The same render functions, HTML
+ classes, charts, tables, spacing, and responsive rules are used for both
+ providers. Claude and Codex time-series charts stay width-aligned while dense
+ content scrolls within its own region; Codex recommendations use indexed
+ 30-day structural evidence.
+- **Truthful names, no invented concepts** — root tasks use the latest real
+ thread title after path redaction. Child rows prefer their own real thread
+ title; when it is missing, they use the reported nickname and display the
+ parent/root title; if those are also missing, they receive a localized neutral
+ fallback. Projects use the Git repository name or, outside Git, the directory
+ basename. The Codex view does not manufacture Branches or Workflows that
+ cannot be measured reliably.
+- **Claude / Codex / Compare modes** — keep each provider's meaning intact.
+ Compare shows input, output, and cache side by side; it never adds unrelated
+ costs or quota windows together.
+- **Auditable time and coverage** — rolling 7-day and 30-day totals use exact
+ event-day slices in your configured timezone. During an incomplete migration
+ or rebuild, every Codex card, table, project, session, recommendation, and
+ status value is visibly an **indexed subtotal**; unverified legacy totals are
+ excluded. Once a Codex home is detected, its provider tab appears immediately;
+ the page shows exact indexed-file, percentage, and byte progress during the
+ first build, while Compare waits until both providers have real data. After
+ the first atomic checkpoint, the complete subtotal dashboard stays usable
+ while indexing continues; progress never replaces its cards or tables. A selected Codex home is account-agnostic,
+ so logs left by multiple
+ sign-ins in that same home are combined. Local logs expose no reliable account
+ identity, so limit cards remain **last-observed** and are never summed.
+- **Private, scalable local index** — stores machine-salted pseudonymous keys;
+ numeric and structural aggregates; and sanitized project, directory, agent,
+ model, effort, role, time, and quality metadata. It never persists raw IDs,
+ full paths or repository URLs, thread titles, or conversation bodies. A
+ background worker scans large histories with a default 30-second watcher delay
+ (Off / 10 / 30 / 60 / 120 / 300 seconds).
+ A first-time index or incomplete legacy migration gets one bounded 64 GiB /
+ 16,384-file-pass streaming ceiling; it does not reserve that amount of memory
+ and remains cancellable and resumable. After convergence, background work
+ returns to 128 MiB / 64 file passes and the always-visible Refresh action uses
+ 2 GiB / 512 file passes. Unchanged warm refreshes still read zero usage-record
+ JSONL body bytes.
+- **Provider-aware controls** — the shared Settings tab shows only common and
+ Codex-effective controls when Codex is selected. Codex collection and local
+ Codex recommendations can each be disabled.
+- **Exact-version release notice** — the upgrade message only describes the
+ installed release. It is on by default and can be disabled in Settings.
+
## What's new in 2.2
- **Usage share card** (opt-in, `enableShareCard`) — a themed, configurable
@@ -176,8 +280,10 @@ consent prompt.
icon as a way back into the dashboard.
- **Status-bar metric** (`statusBarMetric`) — keep showing today's cost, or
switch the first item to today's total **token** count (compact k/M).
-- **Weekly Opus limit** (`showOpusWeekly`, opt-in) — append `opus:NN%` to the
- quota item for heavy Opus users. (PR #38, [@wheelbarrel00](https://github.com/wheelbarrel00).)
+- **Model-scoped weekly limit** (`showScopedWeekly`, opt-in) — adds the weekly
+ cap actually named by Anthropic, such as `fable 17%`; migrated from the
+ original model-specific contribution in PR #38 by
+ [@wheelbarrel00](https://github.com/wheelbarrel00).
- **AI advice 2.0** — bring your own key: **Anthropic** (`/v1/messages`) by
default, or any OpenAI-compatible endpoint (`advice.apiFormat`). Fed with the
new signals (runs, cache hit rates, attribution, thinking share); optional
@@ -198,9 +304,10 @@ consent prompt.
## What's new in 2.0
-- **Real 5-hour and weekly quota** in the status bar — reads Claude Code's
- existing OAuth session from `~/.claude/.credentials.json` or the macOS
- Keychain, zero config.
+- **Real 5-hour and weekly quota** in the status bar — reads the OAuth session
+ from the same Claude profile as the window: explicit `dataDirectory`, then
+ the first valid `CLAUDE_CONFIG_DIR`, then `~/.claude`. The macOS Keychain is
+ used only for the default profile.
Adapted from upstream [PR #9](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/9)
by [@Dobidop](https://github.com/Dobidop).
- **Four new tabs**: Sessions, Projects, Content, Branches — all sortable.
@@ -256,7 +363,7 @@ downloaded `.vsix`.
tab — grouped into General, Status bar, Data & refresh, and AI advice &
Optimizer. Changes apply immediately.
-To keep VS Code's own Settings UI uncluttered, only three settings stay there
+To keep VS Code's own Settings UI uncluttered, only four settings stay there
(so they still travel with Settings Sync). Open Settings (`Ctrl+,`) and search
for **`Claude Code Usage`**:
@@ -264,6 +371,7 @@ for **`Claude Code Usage`**:
|---|---|---|
| `language` | `"auto"` | UI language: `auto` / `en` / `de-DE` / `zh-TW` / `zh-CN` / `ja` / `ko` / `pt-BR` / `id`. |
| `dataDirectory` | `""` | Custom Claude data dir; empty = auto-detect. |
+| `codex.dataDirectory` | `""` | Custom Codex home; empty = `CODEX_HOME` or `~/.codex`. |
| `advice.apiKey` | `""` | API key for AI advice + the Usage Optimizer (empty = advice opens a demo instead). |
Everything else — refresh interval, status-bar items, number/date formatting,
@@ -289,6 +397,12 @@ output, cache-write and cache-read, summed by model.
their detected family (Opus / Sonnet / Haiku / GPT / Gemini /
DeepSeek / Kimi / GLM / Qwen) instead of falling back blindly.
+Claude transcript totals are counted by **response identity**, not by JSONL
+row. One response can produce both a `thinking` row and a `text` row carrying
+the same `messageId`, `requestId`, and complete `usage` vector. The extension
+keeps the largest vector for that response once; Claude Code's `stats-cache`
+adds the rows. The two totals can therefore differ.
+
What the status bar does **not** know:
- Your actual Anthropic invoice (discounts, free credits, plan caps).
- Whether your proxy provider charges different rates.
@@ -303,8 +417,21 @@ authoritative.
## Privacy
-- All token / cost / session analysis runs **locally** by reading your
+- All **Claude** token / cost / session analysis runs locally by reading your
`~/.claude/projects/**/*.jsonl` files.
+- Codex usage records are discovered only from `sessions/**/*.jsonl` and
+ `archived_sessions/**/*.jsonl` below your Codex home. Separately, the extension
+ streams exactly `$CODEX_HOME/session_index.jsonl` for the `id` → `thread_name`
+ mapping used by truthful thread titles. Absolute paths in those titles are
+ redacted and the titles remain memory-only. Credentials, databases, and unknown
+ files are not read. Usage-record JSONL lines are streamed and temporarily
+ parsed only for allowlisted metadata; prompt, response, command, and
+ tool-argument fields are not inspected or used for analysis and are never
+ retained. Deterministic insights make no network request.
+- The Codex persistent index stores machine-salted pseudonymous keys, numeric
+ and structural aggregates, and sanitized project, directory, agent, model,
+ effort, role, time, and quality metadata. It never stores raw IDs, full paths
+ or repository URLs, thread titles, or conversation bodies.
- The quota indicator calls **`api.anthropic.com/api/oauth/usage`** using
Claude Code's existing OAuth token. No additional credentials are sent.
- **AI advice** and the **Usage Optimizer** are the only features that call a
@@ -326,10 +453,10 @@ authoritative.
`~/.claude/projects` and `~/.config/claude/projects`.
**Quota row shows `5h:--% wk:--%`**
-- Claude Code's OAuth token is missing or expired. Log in to Claude Code
- once; the extension reads `~/.claude/.credentials.json` where present, or
- the macOS Keychain entry used by Claude Code, and refreshes the bearer if
- needed.
+- Claude Code's OAuth token is missing or expired. Log in to the active Claude
+ profile once. Credentials follow explicit `dataDirectory`, then the first
+ valid `CLAUDE_CONFIG_DIR`, then `~/.claude`; the single global macOS Keychain
+ item is never substituted for a selected custom profile.
**`Get AI Usage Advice` returns 404**
- DeepSeek's current endpoint does **not** use a `/v1` prefix. Use
@@ -363,6 +490,13 @@ authoritative.
restored. Thanks to [@nickearnshaw](https://github.com/nickearnshaw) for
documenting this ([PR #21](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/21)).
+**Token counts are lower than Claude Code's `stats-cache`**
+- A single response can be written as separate `thinking` and `text` transcript
+ rows with the same `messageId`, `requestId`, and complete `usage` vector. The
+ extension counts that response identity once and keeps its largest vector;
+ Claude Code's `stats-cache` sums the rows. The extension does not apply a
+ multiplier to make these different mechanisms agree.
+
**Token counts appear lower than the model provider's own dashboard**
- If you use Claude Code with a third-party proxy that routes requests
through sub-agents or background workflows (e.g. ultracode / dynamic
@@ -413,7 +547,8 @@ Contributors whose upstream PRs / issues are incorporated here:
original status-bar context-window indicator and the `showCost` toggle.
- [@wheelbarrel00](https://github.com/wheelbarrel00) —
[PR #38](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/38), the opt-in
- weekly Opus limit (`showOpusWeekly`) in the status bar.
+ weekly Opus limit in the status bar, which grew into today's API-named
+ `showScopedWeekly`.
- [@brenoneill](https://github.com/brenoneill) —
[PR #14](https://github.com/ClaudeCodeUsage/ClaudeCodeUsage/pull/14), custom
data directory (merged into upstream 1.0.8).
diff --git a/docs/releases/2026-07-20-v2.3.0-release-handoff.zh-CN.md b/docs/releases/2026-07-20-v2.3.0-release-handoff.zh-CN.md
new file mode 100644
index 00000000..031a0f51
--- /dev/null
+++ b/docs/releases/2026-07-20-v2.3.0-release-handoff.zh-CN.md
@@ -0,0 +1,316 @@
+# V2.3.0 Codex Beta R8 发布交接单(中文版)
+
+更新日期:2026-08-13
+
+## 结论
+
+V2.3.0 的方案 B 已完成到本地安装验收阶段。Codex 不再是嵌入式的简化面板,
+而是与现有 Claude 仪表板共用同一套视觉、导航、状态和设置语言的三页产品:
+**概览(Overview)**、**探索(Explore)**、**优化建议(Recommendations)**。
+设置是从当前主页面进入并返回的辅助页面,不是第四个主标签页。
+
+本轮没有把 Claude 的数据模型硬套给 Codex:不估算 Codex 美元成本,不检查、分析或
+保留对话正文字段,也不虚构无法从本地日志可靠获得的 Branches/Workflows。Codex 的额度只
+标为日志中的“最后观测值”,不冒充实时订阅余额。
+
+R5 另外修复了真实环境中“已启用 Codex Beta、重载后仍完全没有结果”的阻塞问题:
+持久索引在一次写入后出现合法 JSON 末尾附加数据,旧版会在加载阶段永久报错,甚至无法
+进入本地日志扫描。现在只对 JSON 损坏或不支持的 schema 执行原子隔离,完整保留
+`.corrupt-*.json` 备份并从空索引重建;权限等其他 I/O 错误仍然失败关闭。
+
+R6 进一步处理本机观察到的 Extension Host 能耗:后台窗口会停止 Claude/Codex
+轮询与文件 watcher;同日、同时区、完整且无变化的 Codex 索引不再重算 aggregate、
+coverage 或写回约 4 MB 索引;多个 VS Code Extension Host 通过同一 index-path
+lease 串行刷新,原子保存也改用每次调用唯一的临时文件。
+
+R7 已合入正式发布的 V2.2.2,并针对维护者指出的“仍像另一款嵌套插件”完成结构级
+视觉统一:标题与操作区、供应商导航、主标签页的顺序现在与 Claude 页面一致;额度
+改用现有摘要卡片,最近任务不再重复显示整套统计,推荐构成改用现有模型明细行。
+这不是另起一套 Codex 皮肤,而是直接复用 Claude 的 header、actions、tabs、
+summary、model details、composition、table、spacing 和响应式规则。
+
+R8 又加入 Claude Today 的正式渲染参照,并继续把 Codex 的模型/effort 明细、推荐构成、
+桌面和移动端 session 列表收敛到同一套组件形态。它还合入当前 `main` 的通用配额窗口
+(#86)和首次回复降级韧性(#88);旧固定模型配置只做一次性迁移,用户看到的是
+Anthropic API 实际返回的模型名称。成就/社区功能只预留了无身份、无网络的本地证据
+契约,未接入 UI、设置、存储、上传或排名,不影响本次 Codex Beta 的运行路径。
+
+## 版本与成品
+
+- 开发分支:`codex/v2.3.0-energy-efficiency`
+- V2.2.1 发布后基线:`2be7f52`
+- R8 已验证运行时代码:`b7473d7`;本轮视觉对齐:`c2626a9`;成就证据契约:`430da27`;
+ V2.2.2 合入:`978e944`。
+- 本地测试包:
+ `/Users/carl/Jiaming/ClaudeCodeUsage-MyFix/claude-code-usage-2.3.0-codex-beta-r8.vsix`
+- VSIX 内部版本:`GrowthJack.claude-code-usage@2.3.0`
+- VSIX 文件数:80
+- VSIX 大小:835,622 bytes
+- SHA-256:`c197b32a8bd7d6b756144a9c41eed93ec3809c69bc0df197451804fdb3580256`
+- 源码 `package.json`、`package-lock.json` 与 lock 根包版本仍为 `2.1.1`;打包通过
+ `vsce package 2.3.0 --no-update-package-json --no-dependencies` 注入测试包版本,
+ 没有污染源码版本。
+- 当前仅生成本地包;未自动安装、未 push、未创建 PR、未发布 Release。
+
+## V2.3.0 实际范围
+
+### 1. 三页 Codex 体验
+
+- **概览**:最近任务、最近 7 天、最近 30 天、全部时间;展示 processed、fresh、
+ cached input、output、reasoning、趋势、构成、覆盖率、质量和最后观测额度。
+- **探索**:项目、会话、模型与推理强度;支持搜索、筛选、排序、图表日期下钻、
+ 项目下钻、父子谱系折叠与状态恢复。
+- **优化建议**:只针对用户选定范围,根据真实聚合证据显示“观测—证据—代理指标—
+ 条件式行动”;没有证据时不生成泛化建议。
+- **设置**:供应商感知的辅助页面,可分别关闭 Codex 数据收集和本地建议;返回时保留
+ 原主页面。Codex watcher 默认安静等待 30 秒,也可关闭或设为 10/60/120/300 秒。
+- 桌面、移动、亮色、暗色均沿用插件原有卡片、柱状图、构成图、表格、间距和
+ VS Code theme token,避免形成“插件里嵌了另一个插件”的视觉割裂。
+
+### 2. 真实 session 与项目名称
+
+- 精确流式读取 `$CODEX_HOME/session_index.jsonl` 的 `id → thread_name` 关系;只把
+ 清洗后的标题保留在内存,不写入持久索引。
+- root 使用最新真实标题;child 优先使用自己的真实标题。child 没有标题时,才使用
+ nickname + parent/root 标题;仍无法确定时使用本地化中性名称。
+- 最近任务的项目显示只从同一规范化项目的记录中选取;匿名 root 不会再阻止同项目
+ child 提供真实项目名,也不会误借其他项目的名称。task key、root 标题和父子谱系保持不变。
+- 标题中的绝对路径会先替换为 `[path]`。项目优先使用 Git 仓库名,否则只使用工作
+ 目录 basename;不会把完整路径、URL 或原始 session ID 暴露到 UI 或索引。
+
+### 3. 数据与统计语义
+
+- 用量记录只发现 `sessions/**/*.jsonl` 与 `archived_sessions/**/*.jsonl`;
+ `session_index.jsonl` 仅用于标题关联。credential、数据库、浏览器状态、Keychain 和
+ 未知文件均不纳入发现范围。
+- 最近 7/30 天按设置时区中的真实事件日滚动计算;覆盖未完成时明确显示 partial,
+ 不把部分回填冒充完整统计。
+- root、child、approval-reviewer 与无法可靠分类的剩余用量分别显示;剩余量明确标为
+ root/unknown-role,不再强行分配成已知角色。
+- Codex 限额是日志中的最后观测快照,过期后隐藏;不是在线 billing API。
+- Claude 继续保留原成本与配额语义。跨供应商比较不会相加不兼容的 cost 或 quota。
+
+### 4. 隐私与持久索引
+
+- 用量 JSONL 会逐行流式、临时解析,以提取白名单内的用量与结构元数据;prompt、
+ response、command、tool argument 字段不会被检查或用于分析,也不会被保留或持久化。
+- 持久索引只保存带机器 salt 的伪匿名 key、数字/结构聚合,以及清洗后的项目/目录、
+ agent、model、effort、role、时间与质量元数据。
+- 持久索引不保存原始 ID、完整路径、URL、thread title 或对话正文。
+- agent nickname、model 和 effort 在解析入口与索引 load/save 两端使用同一白名单净化;
+ 路径、URL、控制字符、UUID 和 64 位伪匿名键都会被丢弃。危险 bucket 归入 `unknown`
+ 并合并 token,避免因清洗而减少总量。
+- worker 支持 append tail、truncate/replace、archive move、失败保留上次可信结果、
+ single-flight、取消和安全 checkpoint。
+- warm refresh 的“零重读”只指未变化的 usage-record/rollout JSONL;精确标题映射文件
+ `session_index.jsonl` 每次仍会流式读取,文档不再把这两类文件混为一谈。
+- 损坏 JSON 与不支持的持久索引 schema 会先被原子改名为带时间戳和进程号的
+ `.corrupt-*.json`,随后从本地 usage records 重新建立;不会从损坏聚合中拼接或猜测
+ 数据。诊断只显示 `invalid-json`/`unsupported-schema` 安全枚举,不包含路径和内容。
+- 本次实际损坏索引已保留为
+ `/Users/carl/Library/Application Support/Code/User/globalStorage/growthjack.claude-code-usage/codex-index-v1.corrupt-manual-20260722.json`
+ (1,336,207 bytes),原活动索引位置已腾空供首次重建。该备份未删除。
+
+### 5. 使用行为优化
+
+- 基于所选范围识别高 effort、多 agent、自动审批审阅、重复检查代理指标、修改后检查
+ 和缓存行为,并给出八种 UI 语言的可粘贴约束。
+- 建议由本地确定性规则从白名单聚合生成,不调用模型或网络,也不检查或使用正文字段。
+- `apply_patch` 轮次、修改后工具调用等只标为结构性代理指标,不宣称它们等于实际文件
+ 数或能够证明因果关系。
+- 这能直接回应 Codex 5.6 sol 在普通任务上容易进入多轮审阅、独立复审和 subagent
+ 扩张的问题:先用本地证据指出发生在哪些范围,再建议限制 effort、审阅轮次或 agent
+ 数量,而不是对所有用户显示同一套空泛提示。
+
+### 6. 发布与维护门禁
+
+- 只有当前有焦点的 VS Code 窗口保留轮询、Claude watcher、Codex watcher 和凭据
+ watcher;恢复焦点时执行一次追赶刷新。后台窗口收到全局设置变化也不会立即重复扫描。
+- Codex worker 在同 manifest、同 civil day、同时区且 coverage 完整时返回
+ `indexChanged=false`,不 clone/重算索引,也不调用原子保存。跨日、时区变化、索引
+ 恢复、迁移未完成或文件变化都会绕过该快速路径。
+- `${indexPath}.lock` 是原子发布的目录租约,目录内唯一的随机 token 文件只保存 PID、
+ token 和时间戳,不含路径、session、project 或用量数据。跨 Extension Host 的
+ load/manifest scan/update/save 被同一 lease 包围;30 分钟 stale 或已退出 PID
+ 可恢复,release/reaper 只能删除自己观察到的 token 文件。50 个独立进程连续三轮
+ 回收同一 dead owner 时最大并发均为 1。旧 R6 候选遗留的文件型 dead lock 可安全升级
+ 回收,live owner 保留,符号链接失败关闭。
+- 原子保存从共享 `${indexPath}.tmp` 改为
+ `${indexPath}.tmp-${process.pid}-${randomUUID()}`,并发保存测试不再出现 rename
+ `ENOENT`,异常清理也不会删除其他调用的临时文件。
+- 发布提醒只匹配当前完整版本,默认开启但可关闭;不会继续提醒旧 V2.2 内容。
+- 配额显示保留通用 5 小时/每周窗口;`showScopedWeekly` 可选显示 Anthropic API
+ 实际命名的模型专属周额度。旧固定模型键不再出现在产品表面,仅在首次升级时迁移
+ 既有偏好。
+- GitHub Actions 分为 Node Tests、UI Test、Release Package 三层;发布任务必须等待逻辑
+ 和浏览器门禁,并在 Marketplace、Open VSX 和 Release 附件之前校验 VSIX。
+- Claude 文件 watcher 只刷新 Claude;Codex watcher 独立遵守自己的安静延迟,因此
+ Claude 的频繁 JSONL 写入不会绕过 Codex 的 120/300 秒低 CPU 扫描选项。
+- VSCE 固定为 3.9.1;包策略拒绝源码、测试、依赖、secret、内部 review 材料、异常路径、
+ 重复条目、错误 runtime main 和版本不一致。
+- Codex 主写 commit 使用 OpenAI Codex footer 与 co-author trailer;历史贡献者 PR 的
+ merge 署名保持不变。
+
+## 最终验证证据
+
+| 门禁 | 环境 | 结果 |
+|---|---|---:|
+| TypeScript + Node/策略测试 | macOS,Node v24 | 604 项:603 pass、0 fail、1 skip |
+| 首次回复专项测试 | macOS,本机回环 HTTP | 26/26 |
+| Webview 全套门禁 | Playwright Chromium macOS | 57/57 |
+| Linux UI 与 canonical 视觉基线 | 固定 `mcr.microsoft.com/playwright:v1.61.1-noble` | 57/57(含视觉 7/7) |
+| VSIX 规则校验 | 仓库内 `verify-vsix.mjs` | 通过,80 files/835,622 bytes |
+| ZIP 完整性 | `unzip -t` | 全部条目 OK |
+| 已安装 VSIX smoke | 隔离 macOS VS Code 1.131.0 | 安装、激活、命令注册、手动刷新与 Codex 页面通过 |
+
+唯一跳过项是 inode 替换 fixture:当前容器文件系统不能为该 fixture 精确保留同一
+mtime;测试明确以这一能力为条件,未发生失败。
+
+### 真实本地数据能耗基准
+
+对当前 `/Users/carl/.codex` 的只读副本索引先执行一次准备刷新,再连续执行五次稳定
+worker refresh;测试索引放在临时目录,没有改写 VS Code 正在使用的索引:
+
+| 指标 | 准备增量刷新 | 后续 5 次稳定刷新 |
+|---|---:|---:|
+| JSONL 文件 | 1,144 | 每次 1,144 |
+| manifest 总字节 | 5,366,437,729 | 每次 5,366,437,729 |
+| metadata scan | 91 ms | 64–75 ms |
+| update/parse | 1,630 ms | 每次 1–3 ms |
+| JSONL body reads | 3 | 每次 0 |
+| `indexChanged` | true | 每次 false |
+| 索引写盘 | 是 | 每次否 |
+
+五次稳定刷新均保留 1,144 个 contribution 的安全项目名,aggregate、文件数和总字节
+保持稳定。R5 中常见的无变化 update/parse 为 340–430 ms;R6 本次连续实测为
+1–3 ms。保留的 64–75 ms 是发现 1,144 个文件变化所必需的 metadata scan,不读取
+JSONL 正文。
+
+七张 Linux Chromium 视觉基线(R8 新增 Claude Today 正式参照页):
+
+| 场景 | 尺寸 | SHA-256 |
+|---|---:|---|
+| Claude Today desktop light 参照 | 1248×1246 | `a98aaca4e3b1e1e0f29f789024b2c2266ff51adf2401a5b9ed4ea82d84eac5cb` |
+| Explore desktop light | 1248×473 | `da2c79240a1b0371cb8fc4fc18a4e143e86fa24166477f8ae4d00f5bd07676ee` |
+| Overview desktop dark | 1248×1643 | `7457271559b17bdc27317b24c5cb27931f0ae6a4a3365b74c60b2ece2371be34` |
+| Overview desktop light | 1248×1643 | `5fccdd1b4ff570aef21119394204e43fa93a59ec0f8157661357162f558d8f98` |
+| Overview mobile light | 328×2508 | `a6943f7f25c3dce56054aa4c3e66c92e6bb25b6e1b348fae62a7ee759216d010` |
+| Recommendations desktop light | 1248×885 | `e74faf39cca4334d07b951aaa4ac0cdfb42ec2f5877ea96dda9f9dc5015c4991` |
+| Sessions mobile light | 328×3209 | `09510ad30e36da489b829b18406c1946d7447360ef869cba0642f094506c0a4d` |
+
+包内没有 `.superpowers`、`.github`、`src`、`out/test`、`tests`、`node_modules`、
+`docs`、agent 状态、credential、secret 或私钥材料。
+
+## 已完成与尚未完成的运行环境验证
+
+- R8 已在全新的 macOS VS Code user-data/extensions 目录安装并激活;后台窗口先按
+ 节能门禁保持静默,聚焦后手动刷新成功索引 1 个合成 session。安装后的真实 webview
+ 显示标题 `V2.3 installed VSIX smoke`、项目 `V230Smoke`、processed 150、fresh 110、
+ cached input 40、output 30、reasoning 10,并正确显示 `gpt-5.6-sol`/`medium`。
+- 本轮已在 macOS 完成全部逻辑/策略门禁,并在 Linux Chromium 完成 57 项真实
+ Webview 测试。
+- 还没有在真实 Linux VS Code Extension Host 上做长期 CPU smoke;因此 Linux 高 CPU
+ 风险已有 worker、single-flight、quiet delay 和自动化保护,但仍建议在 prerelease/
+ PR 阶段观察一次实际 Extension Host CPU。
+
+## 已知 Beta 边界
+
+- Codex 不显示美元成本;本地日志不能可靠支持时,不会补造 Branches/Workflows。
+- 限额不是在线余额;新写入日志出现前,最后观测值可能滞后。
+- 大历史第一次 backfill 仍会消耗磁盘读取,但在可取消 worker 中执行;后续只增量读取
+ 变化的 usage-record/rollout 文件。
+- 产品在 Beta 期间沿用现有 Marketplace identity;是否改名或拆包留待采用率复盘。
+- R8 是本地候选包,不等于已发布版本。真实 Linux Extension Host 长期 CPU smoke 仍
+ 留在 prerelease/PR 阶段执行。
+
+## 本地验收方法
+
+1. VS Code 打开 **Extensions**。
+2. 点击右上角 `…`,选择 **Install from VSIX…**。
+3. 选择项目根目录的
+ `claude-code-usage-2.3.0-codex-beta-r8.vsix`。
+4. 执行 `Developer: Reload Window`。
+5. 重点检查真实 session/项目名称、概览的时段与额度、探索的项目/会话下钻、优化建议
+ 的证据范围,以及 Settings 返回原页面的状态保持。
+
+## 后续发布路径
+
+当前按维护者要求停在本地候选包;没有替维护者 push、开 PR 或发布。完成本地验收后:
+
+1. push `codex/v2.3.0-energy-efficiency`;
+2. 创建 V2.3.0 PR,等待 Node Tests、UI Test、Release Package 全绿后合并;
+3. 检查 Release Drafter 生成的 `v2.3.0` 草稿;
+4. 由维护者点击 **Publish release**。
+
+不要手改源码版本,也不要手建 tag。发布 workflow 会从 `v2.3.0` tag 在构建时写入
+2.3.0,并使用现有 `VSCE_PAT`、`OVSX_PAT` 发布两个 registry 后附加已验证 VSIX。
+
+## 可直接使用的 PR 正文(英文)
+
+```md
+## Summary
+
+- Adds an opt-out, local-only Codex Beta alongside the existing Claude experience, with provider-native processed, fresh, cached-input, output, reasoning, model, effort, coverage, quality, and last-observed limit semantics. Codex dollar cost is intentionally not estimated.
+- Rebuilds Codex as three cohesive destinations in the existing dashboard design system: Overview, Explore, and Recommendations. Settings remains a provider-aware auxiliary page that returns to the previous destination.
+- Adds truthful project and thread identity, searchable and sortable project/session drill-downs, parent/child lineage, exact event-day rolling periods, partial-coverage disclosure, and a truthful root/unknown-role residual instead of inventing classifications.
+- Adds selected-scope, evidence-gated local guidance for effort, multi-agent work, automatic approval reviews, repeated-check proxies, post-patch checks, and cache behavior without inspecting or using conversation fields, which are never retained.
+- Indexes large Codex histories in a cancellable background worker with pseudonymous persistent aggregates, append-tail refreshes, safe recovery, and a quiet configurable watcher.
+- Reduces idle Extension Host work by suspending recurring work in unfocused windows, returning unchanged same-day indexes without recomputation or disk writes, serializing cross-window refreshes with a privacy-safe lease, and isolating atomic temporary files.
+- Adds Node 20, real Chromium Webview, visual-regression, accessibility, responsive, and verified-VSIX release gates.
+
+## How was this tested?
+
+- Node: 604 tests, 603 passed, 0 failed, 1 filesystem-capability fixture skipped.
+- First-pass automation: 26/26 focused tests passed with the loopback HTTP fixture enabled.
+- Playwright Chromium on macOS: 57/57 interaction, accessibility, responsive, and local visual checks passed.
+- The complete fixed Linux Chromium gate passed 57/57, including seven manually inspected canonical R8 visual baselines and the Claude Today parity reference.
+- A read-only real-data benchmark over 1,144 files / 5.37 GB completed five consecutive stable refreshes with 0 body reads, 1–3 ms update/parse, and no index writes while preserving aggregates, file count, and project names.
+- The R8 VSIX passed the repository package verifier and ZIP integrity check: 80 files, 835,622 bytes, SHA-256 `c197b32a8bd7d6b756144a9c41eed93ec3809c69bc0df197451804fdb3580256`.
+- The R8 VSIX installed and activated in an isolated macOS VS Code profile; a synthetic Codex session rendered its exact title, project, token buckets, model, and effort after manual refresh. A real Linux VS Code Extension Host CPU smoke remains recommended during prerelease review.
+
+## Checklist
+
+- [x] Claude-only behavior remains available
+- [x] Codex-only and both-provider selection are covered
+- [x] No fabricated Codex dollar cost, Branches, or Workflows
+- [x] Real thread/project identity and privacy boundaries are covered
+- [x] All eight UI locales and seven README editions are synchronized
+- [x] Node, browser, accessibility, responsive, visual, and package gates pass
+- [x] `CHANGELOG.md` updated
+- [x] Codex-led commits use the OpenAI Codex co-author trailer
+- [ ] Real Linux Extension Host CPU smoke (recommended during prerelease review)
+
+---
+🤖 Generated with [OpenAI Codex](https://developers.openai.com/codex/)
+```
+
+## 可直接使用的 Release notes(英文)
+
+```md
+## V2.3.0 — Codex Beta and evidence-based usage coaching
+
+### Added
+
+- **A cohesive three-page Codex dashboard**: Overview summarizes recent, 7-day, 30-day, and all-time usage; Explore covers projects, sessions, models, and effort; Recommendations turns selected-scope evidence into conditional actions. All three use the existing Claude dashboard's cards, charts, tables, spacing, and VS Code theme tokens.
+- **Truthful local identity**: real Codex thread titles are streamed from the local session index, child threads prefer their own titles, and projects use Git repository names or safe folder basenames. Titles are path-redacted and never persisted.
+- **Provider-native usage semantics**: processed, fresh, cached input, output, reasoning, model, effort, lineage, coverage, quality, and last-observed limits are shown without estimating Codex dollar cost or inventing Branches and Workflows.
+- **Evidence-gated local recommendations**: selected scopes can surface high effort, multi-agent work, automatic approval reviews, repeated-check proxies, post-patch checks, and cache patterns. Guidance runs locally without a model or network call, does not inspect or use conversation fields, and never retains them.
+- **Scalable private indexing**: a cancellable worker stores only pseudonymous keys, safe metadata, and numeric/structural aggregates, then refreshes changed rollout tails instead of rereading unchanged usage files.
+- **Release quality gates**: Node tests, real Chromium interactions, accessibility, eight-locale responsive coverage, seven visual baselines including a Claude parity reference, and strict VSIX verification now gate publishing.
+
+### Changed
+
+- Release announcements now match only the exact installed version and remain default-on but disableable.
+- Codex watching defaults to a quiet 30-second delay and can be disabled or extended to 300 seconds.
+- Unfocused VS Code windows suspend recurring Claude and Codex refresh resources. Complete same-day Codex indexes skip unchanged recomputation and persistence, while a cross-process lease prevents duplicate multi-window scans and unique temporary files prevent atomic-save collisions.
+- Generic 5-hour and weekly windows remain, while `showScopedWeekly` can opt into the model-scoped weekly cap dynamically named by Anthropic; the retired fixed-model key is migration-only.
+
+### Privacy and boundaries
+
+- Usage discovery is restricted to local session and archived-session rollout JSONL. A separate exact session index is streamed only for title mapping.
+- Credentials, databases, browser state, Keychain state, unknown files, raw IDs, full paths, URLs, titles, prompts, responses, commands, and tool arguments are excluded from persistent storage.
+- Limits are last-observed local-log snapshots, not live billing data. Codex cost is not estimated.
+
+---
+🤖 Generated with [OpenAI Codex](https://developers.openai.com/codex/)
+```
diff --git a/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.en.md b/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.en.md
new file mode 100644
index 00000000..5e0b8983
--- /dev/null
+++ b/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.en.md
@@ -0,0 +1,168 @@
+# V2.3.0 R10 Final Pre-release Audit Handoff
+
+Updated: 2026-08-27
+
+Branch: `codex/v2.3.0-lineage-r6`
+Upstream PR: `ClaudeCodeUsage/ClaudeCodeUsage#93`
+
+## Decision summary
+
+This follow-up fixes the real 20-GB-class Codex experience that could remain in
+an indexing state for hours, and promotes the #87 Claude changed-file prototype
+into the production refresh path. It does not redefine Claude or Codex token,
+cache, reasoning, request-replay, active/archive deduplication, or lineage
+ownership semantics. It computes the same semantics with less repeated parsing,
+global reconciliation, aggregation, and index persistence.
+
+After the final gates and installed VSIX check recorded below, Claude should
+perform one read-only final audit. If it finds no new P0/P1 issue, PR #93 is ready
+for the maintainer's merge decision. The audit must not merge, tag, or publish.
+
+## Production changes
+
+### Codex backfill throughput
+
+- First or incomplete backfills use an adaptive local file-pass pool: half the
+ logical CPUs, capped at six workers. A 16-logical-CPU Mac uses six workers.
+- Independent main, lineage, period, and identity file passes run concurrently;
+ outcomes are applied in stable order. Regression coverage deep-compares the
+ complete parallel and sequential indexes.
+- JSONL stream chunks are 1 MiB. The Codex line safety limit remains 1 MiB;
+ Claude explicitly uses an unlimited line length when reusing the scanner.
+- UI progress stays frequent. Large durable snapshots are saved at roughly
+ 10 seconds, 2 GiB, or 256 completed passes, plus stage boundaries. Cancellation,
+ atomic resume, and crash recovery remain bounded without making repeated
+ tens-of-megabytes writes dominate a fast rebuild.
+- A small warm append has one durable checkpoint. A worker whose final
+ checkpoint already saved the result skips a duplicate final save.
+- Global lineage reconciliation runs only in stages that can change lineage.
+- The pool is disposed after convergence. Steady automatic and manual budgets
+ remain 64 passes / 128 MiB and 512 passes / 2 GiB respectively; an unchanged
+ refresh reads no usage JSONL body and writes no index.
+
+### Stable ambiguity is not unfinished indexing
+
+After base and required period coverage complete, unresolved duplicate-session
+identity is exposed as `ambiguous-session-identity`. Both unproven local copies
+remain counted and the quality warning remains visible, but the dashboard no
+longer claims that I/O is still running forever. Token totals are unchanged.
+
+### Claude #87 production incremental index
+
+- `claudeIncrementalIndex.ts` is now the Extension Host's production Claude
+ usage refresh path.
+- Per-file contributions retain a verified offset and tail signature. Append
+ reads only the tail; truncate, replace, move, and delete rebuild affected
+ files only.
+- Cross-file response identity, request-ID cardinality and missing-ID fallback,
+ prompt retry handling, first-owner content UUIDs, titles, context, sessions,
+ projects, branches, workflows, costliest messages, and time buckets retain
+ the established full-loader results.
+- Common dashboard aggregates and rows are materialized incrementally.
+ Diagnostics add anonymous `bodyReads` and `aggregateMutations` only.
+- The index is in memory for v2.3.0. A new Extension Host performs one cold
+ build; watcher-driven updates in the same runtime are incremental. This fixes
+ #87 without adding a second persisted Claude index in this release.
+
+## Semantics and privacy invariants
+
+- Valid Codex `last_token_usage` remains exact request attribution;
+ `total_tokens` remains active context. Only missing-last records use the
+ cumulative high-water fallback.
+- Cached input and reasoning remain input/output subsets and are never added or
+ priced twice.
+- API-equivalent cost applies current official API token prices to known models;
+ unknown models remain unpriced. It is not a bill or subscription charge.
+- Raw Codex IDs and paths remain short-lived local worker data. Persistence
+ contains only machine-salted keys and allowlisted numeric/structural metadata.
+ No credential, SQLite, prompt, response, command, or tool-argument data is read.
+- Claude JSONL remains read-only. No runtime dependency or Claude disk index was
+ added.
+
+## Real-corpus performance evidence
+
+The benchmarks used temporary or in-memory indexes and did not modify the live
+VS Code index or either provider's logs.
+
+### Codex full cold rebuild with final checkpoint settings
+
+| Metric | Result |
+|---|---:|
+| Local JSONL files | 2,117 |
+| Manifest bytes | 20,787,066,773 |
+| Main + lineage/pass bytes read | 34,749,362,439 |
+| File/body passes | 2,529 |
+| Empty index to `pending=false` | 66,688 ms |
+| Durable checkpoints | 21 |
+| Final coverage | 2,117 / 2,117 files and all manifest bytes |
+
+While this Codex task continuously appended logs, representative tail refreshes
+spent 0.38–0.41 seconds scanning metadata and 1.2–1.3 seconds parsing plus
+atomically saving the roughly 40-MB index, reading only 2–7 passes and tens of
+KB to about 1.1 MB. In a no-change window, metadata scan was about 0.34 seconds,
+core update about 5 ms, `bodyReads=0`, and `checkpointWrites=0`.
+
+### Claude real local corpus
+
+| Metric | Result |
+|---|---:|
+| JSONL files | 681 |
+| Bytes | 352,730,557 |
+| Visible records | 18,242 |
+| Cold in-memory index | 3,337 ms and 681 body reads |
+| Unchanged manifest scan | 19 ms |
+| Unchanged incremental update | 8 ms, zero body reads/lines/mutations |
+| Materialized dashboard snapshot | 3 ms |
+| Heap after cold build | about 188 MiB |
+
+## Automated gates and package
+
+- TypeScript + Node: 588 tests, 587 passed, 0 failed, 1 capability-gated skip.
+- Playwright Chromium: 53/53 passed across interactions, accessibility,
+ eight-locale responsive checks, and visual contracts.
+- VSIX uses pinned `@vscode/vsce@3.9.1` to inject 2.3.0; source manifests remain
+ at 2.1.1 as required by the release workflow.
+- Final candidate: `claude-code-usage-2.3.0-codex-beta-r10.vsix`, 82 files,
+ 872,284 bytes, SHA-256
+ `1215c1c31584908cbda7d41d722bd473ef19d0f68315bf8b4214f02a563103fa`.
+- Package policy and ZIP integrity passed. The archive includes the three new
+ production runtimes and excludes prototypes, benchmarks, tests, and source.
+ An isolated macOS VS Code install succeeded and listed
+ `growthjack.claude-code-usage@2.3.0`.
+
+## Accepted boundaries
+
+- The Claude index is in memory in v2.3.0 and cold-builds once per Extension Host.
+- The guarded Codex `session_index.jsonl` title stream remains separate from the
+ zero-body-read promise for usage JSONL.
+- Stable Codex identity ambiguity retains both copies and a quality warning; it
+ is not an active indexing task.
+- $20/$100/$200 subscription tiers are not auto-assigned. Local logs have no
+ reliable account-plus-plan identity; any future mapping must be explicit and
+ opt-in.
+- Provider-specific visual restyling remains follow-up work; this release keeps
+ the shared dashboard design system.
+
+## Final Claude audit focus
+
+1. Prove parallel and sequential Codex indexes are equivalent across aggregate,
+ coverage, lineage, and persisted DTOs; verify cancellation and resume.
+2. Verify checkpoint cadence cannot miss the terminal state and does not repeat
+ the final save.
+3. Verify ambiguity copy changes only the false indexing label and neither hides
+ the warning nor drops a contribution.
+4. Compare Claude incremental cold/warm/append/truncate/replace/move/delete with
+ the full loader, especially response/request-ID deduplication and content UUID
+ ownership.
+5. Confirm normal Extension Host refresh no longer invokes the old full-record
+ aggregation path and no-change refresh has zero body reads and mutations.
+6. Run Node, UI, and package verification. Confirm the VSIX contains
+ `claudeIncrementalIndex.js`, `codexFilePassPool.js`, and
+ `codexFilePassWorker.js`, but no prototype, tests, source, secrets, or
+ internal review files.
+7. Report only evidence-backed P0/P1/P2 findings. Do not block v2.3.0 for the
+ workflow-injected version, the maintainer-approved feature/minor labels, or
+ accepted follow-up boundaries.
+
+---
+🤖 Generated with [OpenAI Codex](https://developers.openai.com/codex/)
diff --git a/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.zh-CN.md b/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.zh-CN.md
new file mode 100644
index 00000000..55dfc8bf
--- /dev/null
+++ b/docs/releases/2026-08-27-v2.3.0-r10-final-audit-handoff.zh-CN.md
@@ -0,0 +1,149 @@
+# V2.3.0 R10 发版前最终审计交接(中文版)
+
+更新日期:2026-08-27
+
+分支:`codex/v2.3.0-lineage-r6`
+上游 PR:`ClaudeCodeUsage/ClaudeCodeUsage#93`
+
+## 结论
+
+本轮针对真实 20GB 级 Codex 历史中“长时间停在索引中”的体验完成了生产修复,
+同时把 #87 的 Claude changed-file 增量方案从被排除的原型正式接入运行路径。
+改动没有重新定义 Claude 或 Codex 的 Token、缓存、reasoning、request replay、
+active/archive 去重或 lineage 所有权语义;重点是让相同语义以更少的重复解析、
+全局重算和索引写盘完成。
+
+当前建议:在下述完整门禁与 VSIX 安装验证再次通过后,可由 Claude 做最后只读审计;
+若没有新的 P0/P1 发现,PR #93 可进入维护者 merge 决策。不要在审计中发布 Release、
+创建 tag 或合并 PR。
+
+## 本轮生产变更
+
+### 1. Codex 一次性回填真正使用本机性能
+
+- `codexIndexWorker` 只在首次或未完成回填时创建本地 file-pass pool;worker 数为
+ 逻辑 CPU 的一半,上限 6。本机 16 逻辑 CPU 因此使用 6 个 worker。
+- main、lineage、period、identity 四类相互独立的 per-file pass 可并行执行;结果仍按
+ 原顺序提交,串行路径与并行路径的完整索引有 deep-equality 回归测试。
+- JSONL 流 chunk 从小块提高为 1 MiB;Codex 单行安全上限仍为 1 MiB。Claude 复用同一
+ scanner 时明确传入无穷单行上限,保留其既有超大首条记录语义。
+- 实时 UI progress 仍频繁更新;大型持久索引约每 10 秒、2 GiB 或 256 个完成 pass
+ 保存一次,并在阶段边界保存。这样保留取消、崩溃恢复和原子续建,同时避免数十 MB
+ 索引文件反复写盘主导回填耗时。
+- 小型 warm append 只做一次 durable checkpoint;worker 已经由最终 checkpoint 保存时,
+ 不再重复做第二次 final save。
+- 全局 lineage 只在可能改变它的阶段 reconcile;lineage/period 等后续阶段复用已验证
+ 关系,不再对同一大索引重复执行多轮全局核对。
+- 回填完成后 pool 被销毁;正常自动刷新恢复到 64 pass / 128 MiB 的低功耗预算,
+ 手动刷新仍是 512 pass / 2 GiB。无变化 refresh 不读 usage JSONL body,也不写索引。
+
+### 2. “歧义”不再伪装成“仍在索引”
+
+- base 与所需 period coverage 完成后,重复 session 的稳定身份歧义只显示为
+ `ambiguous-session-identity` 数据质量说明。
+- 两份无法证明相同的本地副本继续全部保留,不做猜测性去重;但它们不会让页面永久显示
+ 「索引仍在进行」。这只改变终态表达,不改变 Token 总量。
+
+### 3. Claude #87 进入正式运行路径
+
+- `claudeIncrementalIndex.ts` 现在是 Extension Host 内 Claude 用量刷新的生产路径。
+- 每个文件保留内存 contribution、已验证 offset 与 tail signature;append 只读 tail,
+ truncate、replace、move、delete 只处理受影响文件。
+- Cross-file response identity、同 message 的 request-ID cardinality、缺失 request-ID 回退、
+ prompt retry、content UUID 首 owner、title、context、session/project/branch/workflow、
+ costliest message 与时段 bucket 均通过增量更新保持旧 full-loader 的精确结果。
+- Dashboard 常用 aggregate 和 row 已物化;一个文件变化不会再对全部 records 重跑所有
+ 聚合。诊断新增匿名 `bodyReads` 与 `aggregateMutations`,不记录路径、ID 或正文。
+- 索引目前仅驻留内存:新的 Extension Host 会执行一次 cold build;同一运行期的 watcher
+ 事件和刷新走增量路径。这正面解决 #87 的“每次 changed-file 都重读全语料”,但没有在
+ V2.3.0 新增第二份 Claude 持久索引文件。
+
+## 统计与隐私不变量
+
+- Codex valid `last_token_usage` 仍提供精确 request component;其 `total_tokens` 仍是
+ active context,不是 request 用量。缺失 last snapshot 时才使用 cumulative high-water。
+- Cached input 仍是 input 子集,reasoning 仍是 output 子集,均不重复计费或重复计入
+ processed。
+- API 等效成本只对已知模型按当前官方 API Token 单价计算;未知模型不定价,结果不是
+ 账单或订阅扣费。
+- Codex raw ID/path 只存在于短期本地 worker memory;持久化仍只有 machine-salted key、
+ 数字与白名单结构 metadata。没有读取 credential、SQLite、prompt、response、command
+ 或 tool argument。
+- Claude JSONL 保持只读;增量索引不新增磁盘持久化,不新增 runtime dependency。
+
+## 真实语料性能证据
+
+所有测试都使用临时索引或内存索引,没有改写 VS Code 正在使用的 Codex 索引,也没有修改
+Claude/Codex 日志。
+
+### Codex:空索引全冷重建(最终 checkpoint 参数)
+
+| 指标 | 结果 |
+|---|---:|
+| 本地 JSONL 文件 | 2,117 |
+| manifest 容量 | 20,787,066,773 bytes |
+| main + lineage 等实际读取 | 34,749,362,439 bytes |
+| file/body pass | 2,529 |
+| 空索引到 `pending=false` | 66,688 ms |
+| durable checkpoint | 21 |
+| 最终 coverage | 2,117 / 2,117,20,787,066,773 / 20,787,066,773 bytes |
+
+运行中的本 Codex 任务持续追加日志时,代表性 tail refresh 为:metadata scan 0.38–0.41 秒,
+核心增量解析 + 40MB 级原子保存约 1.2–1.3 秒,只读 2–7 个 pass、数十 KB 到约 1.1MB。
+在没有新写入的稳定窗口,实测 metadata scan 约 0.34 秒、核心 update 约 5 ms、
+`bodyReads=0`、`checkpointWrites=0`。
+
+### Claude:真实本地语料
+
+| 指标 | 结果 |
+|---|---:|
+| JSONL 文件 | 681 |
+| 总容量 | 352,730,557 bytes |
+| 可见 record | 18,242 |
+| cold 内存索引 | 3,337 ms,681 body reads |
+| unchanged manifest scan | 19 ms |
+| unchanged 增量 update | 8 ms,0 body reads,0 parsed lines,0 aggregate mutations |
+| materialized dashboard snapshot | 3 ms |
+| cold 后 heap | 约 188 MiB |
+
+## 自动门禁与候选包
+
+- TypeScript + Node:588 项,587 pass、0 fail、1 条按文件系统能力 skip。
+- Playwright Chromium:53/53 pass,覆盖交互、可访问性、八 locale 响应式与视觉契约。
+- VSIX:使用固定 `@vscode/vsce@3.9.1` 注入 2.3.0,源码 `package.json` 仍为 2.1.1。
+- 最终候选包:`claude-code-usage-2.3.0-codex-beta-r10.vsix`,82 个文件,
+ 872,284 bytes,SHA-256
+ `1215c1c31584908cbda7d41d722bd473ef19d0f68315bf8b4214f02a563103fa`。
+- 包策略验证和 ZIP 完整性均通过;包内包含三个新的生产 runtime,且不包含 prototype、
+ benchmark、test 或 source。隔离 macOS VS Code 安装成功并列出
+ `growthjack.claude-code-usage@2.3.0`。
+
+## 已知且接受的边界
+
+- Claude per-file index 在 V2.3.0 为内存索引,Extension Host restart 后会 cold build 一次。
+- Codex `session_index.jsonl` 的安全 title mapping 仍会单独流式读取,不属于 usage JSONL
+ zero-body-read 承诺。
+- Codex 稳定身份歧义会保留双方并显示质量 warning;它不再显示为活动索引任务。
+- 订阅档位(20/100/200 美元)仍未自动归属账号。本地日志缺少可靠 account + plan identity;
+ 后续只能采用用户明确 opt-in 的账号映射,不能猜测。
+- Provider-specific 视觉风格适配继续留给后续版本;本次保持共享 dashboard 设计系统。
+
+## Claude 最终审计重点
+
+1. 独立确认并行与串行 Codex index 对全部 aggregate、coverage、lineage 和 persisted DTO
+ 完全等价,取消和 checkpoint 仍可恢复。
+2. 独立确认新的 checkpoint cadence 没有造成未保存的终态,也没有重复 final save。
+3. 独立确认 identity ambiguity 只解除错误的「仍在索引」文案,不隐藏质量 warning、
+ 不删除任何 contribution。
+4. 独立比较 Claude incremental cold/warm/append/truncate/replace/move/delete 与旧 full loader,
+ 尤其检查 response/request-ID 去重与 content UUID ownership。
+5. 检查 Extension Host 正常 refresh 不再调用旧全 records 聚合路径;无变化时 body read 与
+ aggregate mutation 均为 0。
+6. 运行完整 Node、UI、package verifier,并检查 VSIX 确实包含
+ `claudeIncrementalIndex.js`、`codexFilePassPool.js`、`codexFilePassWorker.js`,不包含
+ prototype、test、source、secret 或内部 review 文件。
+7. 只报告由证据支持的 P0/P1/P2;不要因为版本号仍由 release workflow 注入、feature/minor
+ label 组合或已接受的后续边界阻塞 V2.3.0。
+
+---
+🤖 Generated with [OpenAI Codex](https://developers.openai.com/codex/)
diff --git a/docs/superpowers/plans/2026-07-20-v2.3.0-codex-dashboard-completion.zh-CN.md b/docs/superpowers/plans/2026-07-20-v2.3.0-codex-dashboard-completion.zh-CN.md
new file mode 100644
index 00000000..e6ec8878
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-20-v2.3.0-codex-dashboard-completion.zh-CN.md
@@ -0,0 +1,121 @@
+# v2.3.0 Codex 仪表板完成计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**目标:** 将现有 Codex R2 从静态聚合页补齐为可本地试用的 v2.3.0 Beta,首先保证 Session 与项目名称真实准确,再完成统一外壳、筛选下钻、完整额度和 provider-aware 设置。
+
+**架构:** 新增只处理身份元数据的 `codexIdentity.ts`;rollout 索引只持久化安全项目标签和子代理 nickname,`session_index.jsonl` 的线程标题在 provider 刷新时流式读取并仅保留于内存。`codexUsage.ts` 形成带 lineage、真实名称和项目下钻的 view model,`codexView.ts` 继续复用 Claude 的组件语言,`webview.ts` 只处理共享外壳和交互。
+
+**技术栈:** TypeScript 5.8、Node `node:test`、VS Code Webview、现有增量 JSONL worker;不新增 runtime dependency。
+
+## 全局约束
+
+- 不读取 prompt、response、command 或 tool argument 正文。
+- 不显示或持久化完整路径、原始 session ID、仓库 URL、credential 或私有数据库内容。
+- 不估算 Codex 美元成本,不制造 Branches/Workflows。
+- Session 标题来自 `session_index.jsonl`;项目名来自 rollout `session_meta` 的 Git URL 或 `cwd` basename。
+- 无真实名称时明确降级,不使用顺序编号冒充名称。
+- 旧索引身份补全必须有界,不得重读全部 token 历史。
+- Reasoning 是 output 子集,不重复计数。
+- 八个 UI locale 同步;若更新 README,七份 README 同步。
+- 每个生产行为先写失败测试并确认 RED,再写最小实现并确认 GREEN。
+- Codex 主导 commit 使用 `Co-authored-by: OpenAI Codex <215057067+openai-codex[bot]@users.noreply.github.com>` 与 Codex 生成页脚。
+
+---
+
+### 任务 1:安全身份元数据与真实名称来源
+
+**文件:** 新建 `src/providers/codex/codexIdentity.ts`;修改 `codexParser.ts`、`codexIndex.ts`;新增/修改对应 identity、parser、index 测试。
+
+**接口:**
+
+```ts
+export function pseudonymousIdentityKey(salt: string, raw: string): string;
+export function safeProjectIdentity(
+ cwd?: string,
+ repositoryUrl?: string,
+): { keySource?: string; name?: string; directoryName?: string };
+export function loadCodexSessionTitles(
+ codexHome: string,
+ salt: string,
+): Promise