feat: introduce Forever Mode (Sisyphus, Confucius, and Bicameral Voice)

- Sisyphus: auto-resume timer with schedule_work tool
- Confucius: built-in sub-agent for knowledge consolidation before compression
- Hippocampus: in-memory short-term memory via background micro-consolidation
- Bicameral Voice: proactive knowledge alignment on user input
- Archive compression mode for long-running sessions
- Onboarding dialog for first-time Forever Mode setup
- Refresh system instruction per turn so hippocampus reaches the model
This commit is contained in:
Sandy Tao
2026-03-02 16:39:05 -08:00
parent 0d69f9f7fa
commit 2ed06d69dd
54 changed files with 3351 additions and 677 deletions
+58
View File
@@ -64,6 +64,10 @@ vi.mock('fs', async (importOriginal) => {
isDirectory: vi.fn().mockReturnValue(true),
}),
realpathSync: vi.fn((path) => path),
promises: {
...actual.promises,
mkdir: vi.fn().mockResolvedValue(undefined),
},
};
});
@@ -258,6 +262,11 @@ describe('Server Config (config.ts)', () => {
sessionId: SESSION_ID,
model: MODEL,
usageStatisticsEnabled: false,
sisyphusMode: {
enabled: false,
idleTimeout: 1,
prompt: 'continue workflow',
},
};
describe('maxAttempts', () => {
@@ -1870,6 +1879,11 @@ describe('BaseLlmClient Lifecycle', () => {
sessionId: SESSION_ID,
model: MODEL,
usageStatisticsEnabled: false,
sisyphusMode: {
enabled: false,
idleTimeout: 1,
prompt: 'continue workflow',
},
};
it('should throw an error if getBaseLlmClient is called before refreshAuth', () => {
@@ -1925,6 +1939,11 @@ describe('Generation Config Merging (HACK)', () => {
sessionId: SESSION_ID,
model: MODEL,
usageStatisticsEnabled: false,
sisyphusMode: {
enabled: false,
idleTimeout: 1,
prompt: 'continue workflow',
},
};
it('should merge default aliases when user provides only overrides', () => {
@@ -3065,3 +3084,42 @@ describe('Model Persistence Bug Fix (#19864)', () => {
expect(config.getModel()).toBe(PREVIEW_GEMINI_3_1_MODEL);
});
});
describe('Config hippocampus in-memory storage', () => {
let config: Config;
beforeEach(() => {
config = new Config({
targetDir: '/tmp/test',
sessionId: 'test-session',
model: 'gemini-2.0-flash',
debugMode: false,
cwd: '/tmp/test',
});
});
it('should return empty string when no entries exist', () => {
expect(config.getHippocampusContent()).toBe('');
});
it('should append and retrieve entries', () => {
config.appendHippocampusEntry('[00:00:01] - fact one\n');
config.appendHippocampusEntry('[00:00:02] - fact two\n');
expect(config.getHippocampusContent()).toBe(
'[00:00:01] - fact one\n[00:00:02] - fact two\n',
);
});
it('should enforce max entries limit by dropping oldest', () => {
for (let i = 0; i < 55; i++) {
config.appendHippocampusEntry(`[entry-${i}]\n`);
}
const content = config.getHippocampusContent();
// Oldest 5 entries (0-4) should have been dropped
expect(content).not.toContain('[entry-0]');
expect(content).not.toContain('[entry-4]');
// Entry 5 onward should remain
expect(content).toContain('[entry-5]');
expect(content).toContain('[entry-54]');
});
});