| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import { describe, expect, test } from 'bun:test';
- import { deriveTaskSessionLabel, SessionManager } from './session-manager';
- describe('SessionManager', () => {
- test('keeps most recently used sessions within limit', () => {
- const manager = new SessionManager(2);
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'explorer',
- label: 'first thread',
- });
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-2',
- agentType: 'explorer',
- label: 'second thread',
- });
- manager.markUsed('parent-1', 'explorer', 'task-1');
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-3',
- agentType: 'explorer',
- label: 'third thread',
- });
- const prompt = manager.formatForPrompt('parent-1');
- expect(prompt).toContain('exp-1 first thread');
- expect(prompt).toContain('exp-3 third thread');
- expect(prompt).not.toContain('exp-2 second thread');
- });
- test('clears parent-scoped sessions', () => {
- const manager = new SessionManager(2);
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'oracle',
- label: 'architecture',
- });
- manager.clearParent('parent-1');
- expect(manager.formatForPrompt('parent-1')).toBeUndefined();
- });
- test('includes read context for remembered sessions', () => {
- const manager = new SessionManager(2);
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'explorer',
- label: 'session manager',
- });
- manager.addContext('task-1', [
- { path: 'src/index.ts', lineCount: 42, lastReadAt: 1 },
- {
- path: 'src/multiplexer/session-manager.ts',
- lineCount: 24,
- lastReadAt: 2,
- },
- ]);
- const prompt = manager.formatForPrompt('parent-1');
- expect(prompt).toContain('exp-1 session manager');
- expect(prompt).toContain(
- 'Context read by exp-1: src/multiplexer/session-manager.ts (24 lines), src/index.ts (42 lines)',
- );
- });
- test('filters tiny reads and caps read context files', () => {
- const manager = new SessionManager(2);
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'explorer',
- label: 'large context',
- });
- manager.addContext(
- 'task-1',
- Array.from({ length: 10 }, (_, index) => ({
- path: `file-${index}.ts`,
- lineCount: index === 0 ? 9 : 20 + index,
- lastReadAt: index,
- })),
- );
- const prompt = manager.formatForPrompt('parent-1') ?? '';
- expect(prompt).not.toContain('file-0.ts');
- expect(prompt).toContain('file-9.ts (29 lines)');
- expect(prompt).toContain('(+1 more)');
- });
- test('uses configurable read context thresholds', () => {
- const manager = new SessionManager(2, {
- readContextMinLines: 5,
- readContextMaxFiles: 1,
- });
- manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'explorer',
- label: 'custom thresholds',
- });
- manager.addContext('task-1', [
- { path: 'small.ts', lineCount: 4, lastReadAt: 1 },
- { path: 'medium.ts', lineCount: 5, lastReadAt: 2 },
- { path: 'large.ts', lineCount: 12, lastReadAt: 3 },
- ]);
- const prompt = manager.formatForPrompt('parent-1') ?? '';
- expect(prompt).not.toContain('small.ts');
- expect(prompt).toContain('large.ts (12 lines)');
- expect(prompt).not.toContain('medium.ts');
- expect(prompt).toContain('(+1 more)');
- });
- test('bounds stored read context files to the render cap plus overflow marker', () => {
- const manager = new SessionManager(2, {
- readContextMinLines: 1,
- readContextMaxFiles: 2,
- });
- const remembered = manager.remember({
- parentSessionId: 'parent-1',
- taskId: 'task-1',
- agentType: 'explorer',
- label: 'bounded context',
- });
- manager.addContext(
- 'task-1',
- Array.from({ length: 10 }, (_, index) => ({
- path: `file-${index}.ts`,
- lineCount: 10,
- lastReadAt: index,
- })),
- );
- expect(remembered.contextFiles).toHaveLength(3);
- const prompt = manager.formatForPrompt('parent-1') ?? '';
- expect(prompt).toContain('file-9.ts (10 lines)');
- expect(prompt).toContain('file-8.ts (10 lines)');
- expect(prompt).toContain('(+1 more)');
- expect(prompt).not.toContain('file-0.ts');
- });
- });
- describe('deriveTaskSessionLabel', () => {
- test('prefers description over prompt', () => {
- expect(
- deriveTaskSessionLabel({
- description: 'config schema lookup',
- prompt: 'ignored prompt line',
- agentType: 'explorer',
- }),
- ).toBe('config schema lookup');
- });
- test('falls back to prompt then generic label', () => {
- expect(
- deriveTaskSessionLabel({
- prompt: '\n inspect task resumption support \nmore context',
- agentType: 'explorer',
- }),
- ).toBe('inspect task resumption support');
- expect(
- deriveTaskSessionLabel({
- agentType: 'fixer',
- }),
- ).toBe('recent fixer task');
- });
- });
|