mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
fix: date a working row by its turn, not by the session age
`TuiSessionRow` declared `lastSubmitAt`/`inputTokens`/`outputTokens`, `stateSince()` ordered the WORKING group by the first of them and `renderRowLines()` painted the other two, but nothing ever filled any of them in: the unified list carries none, and the `session:updated` payload that does was discarded (an event only schedules a refetch). So a running turn was dated by its SESSION's creation instead. Measured against the live server before the fix: w65 (created 21h ago, turn started one minute earlier) outranked w67 (created 15 minutes ago, turn started five minutes earlier), the reverse of the rule docs/tui.md states, and the elapsed column read `21h` for a turn a minute old. The token column was unreachable code for the same reason. `fetchLiveSessionMetrics()` reads the three fields from `GET /api/sessions` and `applyLiveMetrics()` folds them onto the rows. That route answers from the server's cached LIGHT state (no terminal buffers): 10-20ms measured, against the ~550ms the unified list in the same `Promise.all` already costs, so it is cheap enough to ride every refresh. It is best-effort like the approvals and tmux reads beside it, because losing the anchor is better than losing the list. A ZERO is treated as unknown rather than merged: `stateSince()` reads `lastSubmitAt ?? createdAt` and 0 is not nullish, so a merged 0 would date every never-submitted session to the epoch. The snapshot path gets the same merge, or `codeman tui --list` would number the WORKING group differently from the dashboard that `codeman tui <n>` indexes into. Verified live: working rows now show 28m/8m (turn age, tokens 280.5k/65.2k) where they showed 21h/34m and no tokens. The e2e assertion fails on master's wiring with `[*] 10m` against a session that pressed Enter one minute ago. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+49
-3
@@ -31,9 +31,12 @@
|
||||
* - Plan usage has no route of its own: the last-known snapshot rides
|
||||
* `GET /api/status` as `planUsage` (`web/plan-usage-latest.ts`) and updates
|
||||
* arrive as `session:statusTelemetry` SSE frames.
|
||||
* - The unified list carries no token counters or turn-start stamp
|
||||
* (`TuiSessionRow.lastSubmitAt`), so those stay unset here; the app layer
|
||||
* merges them from live session state when it wants them.
|
||||
* - The unified list carries no token counters and no turn-start stamp
|
||||
* (`TuiSessionRow.lastSubmitAt`), which the WORKING group is ordered by, so
|
||||
* `fetchLiveSessionMetrics()` reads them from `GET /api/sessions` and
|
||||
* `applyLiveMetrics()` (tui-app) folds them onto the rows. That route answers
|
||||
* from the server's cached LIGHT state (no terminal buffers, ~10ms), which is
|
||||
* what makes it cheap enough to ride every refresh.
|
||||
*
|
||||
* @module tui/tui-client
|
||||
*/
|
||||
@@ -147,6 +150,22 @@ export interface TuiQuickStartResult {
|
||||
caseName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The live-only fields the unified list does not carry, per session id.
|
||||
*
|
||||
* `lastSubmitAt` is the one the dashboard cannot do without: it is the pane's
|
||||
* last Enter, which is what a WORKING row's elapsed column shows and what the
|
||||
* WORKING group is sorted by. Without it a running turn is dated by the
|
||||
* SESSION's creation instead, so a day-old session that started a turn a minute
|
||||
* ago outranks one that has been working for an hour.
|
||||
*/
|
||||
export interface TuiLiveSessionMetrics {
|
||||
sessionId: string;
|
||||
lastSubmitAt?: number;
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
}
|
||||
|
||||
/** Init snapshot, narrowed to the two facts the dashboard header shows. */
|
||||
export interface TuiInitState {
|
||||
version?: string;
|
||||
@@ -507,6 +526,33 @@ export class TuiClient {
|
||||
return data?.sessions ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The turn-start stamp and token counters for every LIVE session.
|
||||
*
|
||||
* `GET /api/sessions` is the light state (`getLightSessionsState()`, itself
|
||||
* cached server-side): no terminal buffers, so this is a ~10ms read next to
|
||||
* the unified list's transcript scan. Rows are narrowed to the three fields
|
||||
* the dashboard actually merges, and a row with no usable id is dropped
|
||||
* rather than folded in under an empty key.
|
||||
*/
|
||||
async fetchLiveSessionMetrics(): Promise<TuiLiveSessionMetrics[]> {
|
||||
const data = await this.requestData<
|
||||
Array<{ id?: unknown; lastSubmitAt?: unknown; inputTokens?: unknown; outputTokens?: unknown }>
|
||||
>('GET', '/api/sessions');
|
||||
if (!Array.isArray(data)) return [];
|
||||
const rows: TuiLiveSessionMetrics[] = [];
|
||||
for (const entry of data) {
|
||||
if (!entry || typeof entry.id !== 'string' || entry.id === '') continue;
|
||||
rows.push({
|
||||
sessionId: entry.id,
|
||||
...(typeof entry.lastSubmitAt === 'number' ? { lastSubmitAt: entry.lastSubmitAt } : {}),
|
||||
...(typeof entry.inputTokens === 'number' ? { inputTokens: entry.inputTokens } : {}),
|
||||
...(typeof entry.outputTokens === 'number' ? { outputTokens: entry.outputTokens } : {}),
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
async fetchApprovals(): Promise<ApprovalItem[]> {
|
||||
const data = await this.requestData<{ approvals?: ApprovalItem[] }>('GET', '/api/approvals');
|
||||
return data?.approvals ?? [];
|
||||
|
||||
Reference in New Issue
Block a user