Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

### Added
- **Live Kimi Code sessions show their context ring.** The row reads the context Kimi measured after the last turn and the window it requested from the tail of the session wire, the same numbers Kimi prints in its own footer.
- **CodeBurn tells you when OpenAI banks a limit reset on your Codex account.** These grants — the "banked" or "goodwill" resets that restore a rate-limit window early — are sometimes announced and sometimes not, and until now the only way to notice one was to go looking. The reset-credit inventory CodeBurn already reads on every Codex quota refresh is now compared against the previous reading: a credit seen for the first time posts one notification naming what was granted, when it landed and how many you have available to use. The first reading after connecting is a baseline, not news; a credit that disappears because you spent it says nothing; a failed fetch or a malformed payload is treated as no opinion rather than as an empty account, so reconnecting does not re-announce what you were already told; and the fired event is persisted next to the existing quota snapshots, so a relaunch does not repeat it. Settings → Notifications gains a switch for it, on by default. The count and the most recent grant also appear in the Codex Plan tab, in the agent-tab quota hover card, and as a `Limit resets · …` line in `codeburn quota` (text and `--format json`), worded identically on both sides. No new endpoint, no new polling loop and no new data source: this reads fields off a response already fetched on the existing cadence, per the rule #702/#724 established. CodeBurn never spends a credit — this is a notice only. (#725)
- **Kimi Code sessions now show up in the live-sessions block.** A Kimi session whose `agents/*/wire.jsonl` was written inside the liveness window is reported as `kimicode`, with its project, the model of its last request and the activity of its sub-agents folded in.

Expand Down
36 changes: 24 additions & 12 deletions src/live-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,29 +296,40 @@ export async function collectLiveSessionInputs(
return [...inputs.values(), ...await collectKimicodeInputs(nowMs, windowMs)]
}

/// Last model the session actually asked for. Tail-only, like the Claude
/// scanner: a running wire file reaches tens of MB and only the end matters.
async function kimicodeModel(wirePath: string): Promise<string | null> {
/// What the tail of the main wire file says about the session: the model it
/// last asked for, the context Kimi itself measured after the last turn, and
/// the window it asked for. Tail-only, like the Claude scanner: a running wire
/// file reaches tens of MB and only the end matters.
async function kimicodeTail(wirePath: string): Promise<{ model: string | null; contextTokens: number | null; contextWindow: number | null }> {
const out = { model: null as string | null, contextTokens: null as number | null, contextWindow: null as number | null }
let text = ''
try {
text = await readTail(wirePath, TAIL_BYTES)
} catch {
return null
return out
}
const lines = text.split('\n')
for (let index = lines.length - 1; index >= 0; index -= 1) {
const line = lines[index]
if (!line || !line.trim()) continue
let record: { type?: unknown; model?: unknown }
let record: { type?: unknown; model?: unknown; maxTokens?: unknown; tokens?: unknown }
try {
record = JSON.parse(line) as { type?: unknown; model?: unknown }
record = JSON.parse(line) as typeof record
} catch {
continue
}
if (record.type !== 'llm.request') continue
if (typeof record.model === 'string' && record.model) return record.model
if (record.type === 'llm.request' && out.model === null) {
if (typeof record.model === 'string' && record.model) out.model = record.model
if (typeof record.maxTokens === 'number' && record.maxTokens > 0) out.contextWindow = record.maxTokens
} else if (record.type === 'token_counting.measured' && out.contextTokens === null) {
if (typeof record.tokens === 'number' && record.tokens >= 0) out.contextTokens = record.tokens
}
if (out.model !== null && out.contextTokens !== null) break
}
return null
// A measurement without a window (or the reverse) draws nothing; the app
// needs both to size the ring.
if (out.contextTokens === null || out.contextWindow === null) { out.contextTokens = null; out.contextWindow = null }
return out
}

/// Kimi Code keeps a directory per session, the session's own turns in
Expand Down Expand Up @@ -358,14 +369,15 @@ export async function collectKimicodeInputs(
const birthtimeMs = state.createdAtMs
? 0
: (await stat(join(sessionDir, 'state.json')).catch(() => null))?.birthtimeMs ?? 0
const tail = await kimicodeTail(join(sessionDir, 'agents', 'main', 'wire.jsonl'))
inputs.push({
id: basename(sessionDir).replace(/^session_/, ''),
provider: 'kimicode',
project: projectFromWorkDir(state.cwd || state.workDir || '', basename(dirname(sessionDir))),
branch: null,
model: await kimicodeModel(join(sessionDir, 'agents', 'main', 'wire.jsonl')),
contextTokens: null,
contextWindow: null,
model: tail.model,
contextTokens: tail.contextTokens,
contextWindow: tail.contextWindow,
startedMs: state.createdAtMs || birthtimeMs,
lastActivityMs: agents.mainMs,
subagentActivityMs: agents.subagentMs,
Expand Down
7 changes: 4 additions & 3 deletions tests/live-sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ describe('collectKimicodeInputs', () => {
}))
await wire(live, 'main', NOW - 30_000, [
{ type: 'llm.request', model: 'k2', modelAlias: 'kimi-code/k2', time: NOW - 120_000 },
{ type: 'llm.request', model: 'k3', modelAlias: 'kimi-code/k3', time: NOW - 30_000 },
{ type: 'llm.request', model: 'k3', modelAlias: 'kimi-code/k3', maxTokens: 1_048_576, time: NOW - 30_000 },
{ type: 'usage.record', model: 'kimi-code/k3', usage: { output: 12 }, time: NOW - 30_000 },
{ type: 'token_counting.measured', tokens: 664_620, time: NOW - 30_000 },
])
await wire(live, 'agent-0', NOW - 5_000, [{ type: 'llm.request', model: 'k3' }])

Expand All @@ -230,8 +231,8 @@ describe('collectKimicodeInputs', () => {
project: 'atlas',
branch: null,
model: 'k3',
contextTokens: null,
contextWindow: null,
contextTokens: 664_620,
contextWindow: 1_048_576,
startedMs: NOW - 3_600_000,
lastActivityMs: NOW - 30_000,
subagentActivityMs: [NOW - 5_000],
Expand Down
Loading