mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 13:39:41 +02:00
chore: version packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+101
-6
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { existsSync, unlinkSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { existsSync, unlinkSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
@@ -360,7 +360,7 @@ describe('StateStore', () => {
|
||||
const store = new StateStore(testFilePath);
|
||||
|
||||
store.addToGlobalStats(1000, 500, 0.05);
|
||||
store.addToGlobalStats(2000, 1000, 0.10);
|
||||
store.addToGlobalStats(2000, 1000, 0.1);
|
||||
|
||||
const stats = store.getGlobalStats();
|
||||
expect(stats.totalInputTokens).toBe(3000);
|
||||
@@ -388,20 +388,20 @@ describe('StateStore', () => {
|
||||
// Simulate active sessions
|
||||
const activeSessions = {
|
||||
'session-1': { inputTokens: 1000, outputTokens: 500, totalCost: 0.05 },
|
||||
'session-2': { inputTokens: 2000, outputTokens: 1000, totalCost: 0.10 },
|
||||
'session-2': { inputTokens: 2000, outputTokens: 1000, totalCost: 0.1 },
|
||||
};
|
||||
|
||||
const aggregate = store.getAggregateStats(activeSessions);
|
||||
|
||||
expect(aggregate.totalInputTokens).toBe(8000); // 5000 + 1000 + 2000
|
||||
expect(aggregate.totalOutputTokens).toBe(4000); // 2500 + 500 + 1000
|
||||
expect(aggregate.totalCost).toBeCloseTo(0.40, 10); // 0.25 + 0.05 + 0.10
|
||||
expect(aggregate.totalCost).toBeCloseTo(0.4, 10); // 0.25 + 0.05 + 0.10
|
||||
expect(aggregate.activeSessionsCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should persist global stats across instances', () => {
|
||||
const store1 = new StateStore(testFilePath);
|
||||
store1.addToGlobalStats(10000, 5000, 0.50);
|
||||
store1.addToGlobalStats(10000, 5000, 0.5);
|
||||
store1.incrementSessionsCreated();
|
||||
store1.flushAll();
|
||||
|
||||
@@ -410,10 +410,105 @@ describe('StateStore', () => {
|
||||
|
||||
expect(stats.totalInputTokens).toBe(10000);
|
||||
expect(stats.totalOutputTokens).toBe(5000);
|
||||
expect(stats.totalCost).toBe(0.50);
|
||||
expect(stats.totalCost).toBe(0.5);
|
||||
expect(stats.totalSessionsCreated).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assembleStateJson', () => {
|
||||
it('should produce output identical to JSON.stringify(state)', () => {
|
||||
const store = new StateStore(testFilePath);
|
||||
|
||||
// Add sessions, tasks, config changes
|
||||
store.setSession('s1', createMockSessionState('s1'));
|
||||
store.setSession('s2', createMockSessionState('s2'));
|
||||
store.setTask('t1', createMockTaskState('t1'));
|
||||
store.setConfig({ maxConcurrentSessions: 5 });
|
||||
store.addToGlobalStats(1000, 500, 0.05);
|
||||
|
||||
// Force a save so assembleStateJson is called
|
||||
store.saveNow();
|
||||
|
||||
// Read persisted file and compare with full JSON.stringify
|
||||
const persisted = JSON.parse(readFileSync(testFilePath, 'utf-8'));
|
||||
const fullStringify = JSON.parse(JSON.stringify(store.getState()));
|
||||
|
||||
expect(persisted).toEqual(fullStringify);
|
||||
});
|
||||
|
||||
it('should correctly persist all sessions after partial update', () => {
|
||||
const store = new StateStore(testFilePath);
|
||||
|
||||
// Create 10 sessions
|
||||
for (let i = 0; i < 10; i++) {
|
||||
store.setSession(`s${i}`, createMockSessionState(`s${i}`));
|
||||
}
|
||||
store.saveNow();
|
||||
|
||||
// Modify only 1 session
|
||||
const updated = createMockSessionState('s3');
|
||||
updated.status = 'idle';
|
||||
updated.pid = 99999;
|
||||
store.setSession('s3', updated);
|
||||
store.saveNow();
|
||||
|
||||
// Read from disk and verify all 10 sessions are present and correct
|
||||
const persisted = JSON.parse(readFileSync(testFilePath, 'utf-8'));
|
||||
expect(Object.keys(persisted.sessions)).toHaveLength(10);
|
||||
|
||||
// The updated session should have the new values
|
||||
expect(persisted.sessions['s3'].status).toBe('idle');
|
||||
expect(persisted.sessions['s3'].pid).toBe(99999);
|
||||
|
||||
// Other sessions should be unchanged
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if (i === 3) continue;
|
||||
expect(persisted.sessions[`s${i}`].id).toBe(`s${i}`);
|
||||
expect(persisted.sessions[`s${i}`].status).toBe('running');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle session removal and prune cache correctly', () => {
|
||||
const store = new StateStore(testFilePath);
|
||||
|
||||
store.setSession('s1', createMockSessionState('s1'));
|
||||
store.setSession('s2', createMockSessionState('s2'));
|
||||
store.setSession('s3', createMockSessionState('s3'));
|
||||
store.saveNow();
|
||||
|
||||
// Remove s2
|
||||
store.removeSession('s2');
|
||||
store.saveNow();
|
||||
|
||||
const persisted = JSON.parse(readFileSync(testFilePath, 'utf-8'));
|
||||
expect(Object.keys(persisted.sessions)).toHaveLength(2);
|
||||
expect(persisted.sessions['s1']).toBeDefined();
|
||||
expect(persisted.sessions['s2']).toBeUndefined();
|
||||
expect(persisted.sessions['s3']).toBeDefined();
|
||||
|
||||
// Verify round-trip: state in memory matches what's on disk
|
||||
const fullStringify = JSON.parse(JSON.stringify(store.getState()));
|
||||
expect(persisted).toEqual(fullStringify);
|
||||
});
|
||||
|
||||
it('should persist the last value after rapid updates to the same session', () => {
|
||||
const store = new StateStore(testFilePath);
|
||||
|
||||
// Rapidly update the same session 100 times
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const session = createMockSessionState('rapid');
|
||||
session.pid = i;
|
||||
store.setSession('rapid', session);
|
||||
}
|
||||
|
||||
// Only one save at the end
|
||||
store.saveNow();
|
||||
|
||||
const persisted = JSON.parse(readFileSync(testFilePath, 'utf-8'));
|
||||
expect(persisted.sessions['rapid']).toBeDefined();
|
||||
expect(persisted.sessions['rapid'].pid).toBe(99); // Last value wins
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Helper functions to create mock state objects
|
||||
|
||||
@@ -1236,7 +1236,24 @@ describe('SubagentWatcher', () => {
|
||||
|
||||
const mockRl = new EventEmitter();
|
||||
mockCreateInterface.mockReturnValue(mockRl);
|
||||
mockCreateReadStream.mockReturnValue({});
|
||||
|
||||
// createReadStream now used for parent transcript reading (stream tail)
|
||||
// Return a stream-like EventEmitter that emits the transcript content
|
||||
mockCreateReadStream.mockImplementation((filepath: string) => {
|
||||
const stream = new EventEmitter() as EventEmitter & { destroy: () => void };
|
||||
stream.destroy = vi.fn();
|
||||
if (typeof filepath === 'string' && filepath.includes('session1.jsonl')) {
|
||||
// Emit parent transcript content on next tick
|
||||
process.nextTick(() => {
|
||||
stream.emit('data', parentTranscript + '\n');
|
||||
stream.emit('end');
|
||||
});
|
||||
} else {
|
||||
// For agent files, emit end immediately (empty content)
|
||||
process.nextTick(() => stream.emit('end'));
|
||||
}
|
||||
return stream;
|
||||
});
|
||||
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
mockReaddirSync.mockImplementation((path: string) => {
|
||||
@@ -1251,14 +1268,6 @@ describe('SubagentWatcher', () => {
|
||||
mtime: new Date(),
|
||||
size: 100,
|
||||
});
|
||||
// Return parent transcript when reading the session transcript
|
||||
// Return empty for subagent file (will fall back, but we want to test parent extraction)
|
||||
mockReadFileSync.mockImplementation((filepath: string) => {
|
||||
if (filepath.includes('session1.jsonl')) {
|
||||
return parentTranscript;
|
||||
}
|
||||
return ''; // Empty subagent file
|
||||
});
|
||||
|
||||
const discoveredHandler = vi.fn();
|
||||
watcher.on('subagent:discovered', discoveredHandler);
|
||||
@@ -1509,14 +1518,11 @@ describe('SubagentWatcher', () => {
|
||||
});
|
||||
|
||||
describe('File Watcher Management', () => {
|
||||
it('should close file watchers on stop', async () => {
|
||||
const mockFileWatcher = { close: vi.fn(), on: vi.fn(), off: vi.fn() };
|
||||
it('should close directory watchers on stop', async () => {
|
||||
const mockDirWatcher = { close: vi.fn(), on: vi.fn(), off: vi.fn() };
|
||||
|
||||
mockWatch.mockImplementation((path: string) => {
|
||||
if (path.endsWith('.jsonl')) return mockFileWatcher;
|
||||
return mockDirWatcher;
|
||||
});
|
||||
// Only directory watchers are created (no per-file watchers)
|
||||
mockWatch.mockReturnValue(mockDirWatcher);
|
||||
|
||||
const mockRl = new EventEmitter();
|
||||
mockCreateInterface.mockReturnValue(mockRl);
|
||||
@@ -1545,8 +1551,8 @@ describe('SubagentWatcher', () => {
|
||||
|
||||
watcher.stop();
|
||||
|
||||
// Watchers should be closed
|
||||
expect(mockFileWatcher.close).toHaveBeenCalled();
|
||||
// Directory watchers should be closed (per-file watchers no longer exist)
|
||||
expect(mockDirWatcher.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should clear idle timers on stop', async () => {
|
||||
|
||||
Reference in New Issue
Block a user