mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildAwayDigest, resolveAwayDigestRange } from '../src/web/away-digest.js';
|
|
import type { LifecycleEntry, RunSummary } from '../src/types.js';
|
|
|
|
const NOW = Date.UTC(2026, 5, 7, 12, 0, 0);
|
|
const HOUR = 60 * 60 * 1000;
|
|
|
|
function summary(sessionId: string, events: RunSummary['events']): RunSummary {
|
|
return {
|
|
sessionId,
|
|
sessionName: `${sessionId} name`,
|
|
startedAt: NOW - 4 * HOUR,
|
|
lastUpdatedAt: NOW - HOUR,
|
|
events,
|
|
stats: {
|
|
totalRespawnCycles: 0,
|
|
totalTokensUsed: 0,
|
|
peakTokens: 0,
|
|
totalTimeActiveMs: 0,
|
|
totalTimeIdleMs: 0,
|
|
errorCount: events.filter((event) => event.severity === 'error').length,
|
|
warningCount: events.filter((event) => event.severity === 'warning').length,
|
|
aiCheckCount: 0,
|
|
lastIdleAt: null,
|
|
lastWorkingAt: null,
|
|
stateTransitions: 0,
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('away digest', () => {
|
|
it('resolves since-last-visit after the stored marker and defaults to 24h', () => {
|
|
expect(resolveAwayDigestRange({ range: 'since-last-visit', lastViewed: NOW - HOUR, now: NOW })).toMatchObject({
|
|
range: 'since-last-visit',
|
|
since: NOW - HOUR,
|
|
until: NOW,
|
|
});
|
|
|
|
expect(resolveAwayDigestRange({ range: 'since-last-visit', now: NOW })).toMatchObject({
|
|
since: NOW - 24 * HOUR,
|
|
until: NOW,
|
|
});
|
|
});
|
|
|
|
it('separates action-required, completed, live, idle, and informational items', () => {
|
|
const lifecycleEntries: LifecycleEntry[] = [
|
|
{ ts: NOW - 10_000, event: 'exit', sessionId: 'failed', name: 'Failed Session', exitCode: 2 },
|
|
{ ts: NOW - 9_000, event: 'exit', sessionId: 'clean', name: 'Clean Exit', exitCode: 0 },
|
|
{ ts: NOW - 8_000, event: 'mux_died', sessionId: 'mux', name: 'Mux Session' },
|
|
];
|
|
|
|
const digest = buildAwayDigest({
|
|
range: resolveAwayDigestRange({ range: '1h', now: NOW }),
|
|
lifecycleEntries,
|
|
runSummaries: [
|
|
summary('ralph', [
|
|
{
|
|
id: 'complete-1',
|
|
timestamp: NOW - 7_000,
|
|
type: 'ralph_completion',
|
|
severity: 'success',
|
|
title: 'Ralph completion detected',
|
|
details: 'Phrase: COMPLETE',
|
|
},
|
|
]),
|
|
summary('error-session', [
|
|
{
|
|
id: 'error-1',
|
|
timestamp: NOW - 6_000,
|
|
type: 'error',
|
|
severity: 'error',
|
|
title: 'Session error',
|
|
details: 'Tool failed',
|
|
},
|
|
]),
|
|
],
|
|
sessions: [
|
|
{ id: 'active', name: 'Active Session', status: 'working' },
|
|
{ id: 'idle', name: 'Idle Session', status: 'idle' },
|
|
],
|
|
dailyTokenStats: [
|
|
{
|
|
date: '2026-06-07',
|
|
inputTokens: 1_000,
|
|
outputTokens: 2_000,
|
|
estimatedCost: 0.18,
|
|
sessions: 2,
|
|
},
|
|
],
|
|
subagents: [
|
|
{
|
|
id: 'agent-1',
|
|
sessionId: 'active',
|
|
description: 'Research complete',
|
|
status: 'completed',
|
|
lastUpdated: NOW - 5_000,
|
|
},
|
|
],
|
|
now: NOW,
|
|
});
|
|
|
|
expect(digest.sections.needsAttention.map((item) => item.sessionId)).toEqual(['failed', 'mux', 'error-session']);
|
|
expect(digest.sections.completed.map((item) => item.sessionId)).toEqual(['ralph']);
|
|
expect(digest.sections.stillRunning.map((item) => item.sessionId)).toEqual(['active']);
|
|
expect(digest.sections.idle.map((item) => item.sessionId)).toEqual(['idle']);
|
|
expect(digest.sections.informational.map((item) => item.sessionId)).toContain('clean');
|
|
expect(digest.sections.informational.some((item) => item.source === 'subagent')).toBe(true);
|
|
expect(digest.totals).toMatchObject({
|
|
needsAttention: 3,
|
|
completed: 1,
|
|
activeSessions: 2,
|
|
inputTokens: 1_000,
|
|
outputTokens: 2_000,
|
|
estimatedCost: 0.18,
|
|
tokenWindowPrecision: 'day',
|
|
});
|
|
expect(digest.dataFreshness).toMatchObject({
|
|
lifecyclePersisted: true,
|
|
tokenStatsPersisted: true,
|
|
runSummariesLiveOnly: true,
|
|
subagentsLiveOnly: true,
|
|
});
|
|
});
|
|
|
|
it('excludes entries outside the selected range', () => {
|
|
const digest = buildAwayDigest({
|
|
range: resolveAwayDigestRange({ range: '1h', now: NOW }),
|
|
lifecycleEntries: [
|
|
{ ts: NOW - 2 * HOUR, event: 'mux_died', sessionId: 'old' },
|
|
{ ts: NOW - 1000, event: 'mux_died', sessionId: 'recent' },
|
|
],
|
|
runSummaries: [],
|
|
sessions: [],
|
|
dailyTokenStats: [],
|
|
subagents: [],
|
|
now: NOW,
|
|
});
|
|
|
|
expect(digest.sections.needsAttention.map((item) => item.sessionId)).toEqual(['recent']);
|
|
});
|
|
});
|