feat: introduce Forever Mode with A2A listener

- 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
- Auto-start A2A HTTP server when Forever Mode + Sisyphus enabled
- Bridge external messages into session and capture responses
- Display A2A port in status bar alongside Sisyphus timer
This commit is contained in:
Sandy Tao
2026-03-03 21:39:53 -08:00
parent e5d58c2b5a
commit 79ea865790
50 changed files with 3704 additions and 654 deletions
@@ -0,0 +1,86 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MemoryConsolidationService } from './memoryConsolidationService.js';
import type { Config } from '../config/config.js';
describe('MemoryConsolidationService', () => {
let mockConfig: Config;
let service: MemoryConsolidationService;
let mockGenerateContent: ReturnType<typeof vi.fn>;
let mockAppendHippocampusEntry: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.resetAllMocks();
mockGenerateContent = vi.fn().mockResolvedValue({
text: 'Mocked consolidated fact.',
});
mockAppendHippocampusEntry = vi.fn();
mockConfig = {
getIsForeverMode: vi.fn().mockReturnValue(true),
getBaseLlmClient: vi.fn().mockReturnValue({
generateContent: mockGenerateContent,
}),
appendHippocampusEntry: mockAppendHippocampusEntry,
} as unknown as Config;
service = new MemoryConsolidationService(mockConfig);
});
it('should not do anything if isForeverMode is false', () => {
vi.mocked(mockConfig.getIsForeverMode).mockReturnValue(false);
service.triggerMicroConsolidation([
{ role: 'user', parts: [{ text: 'test' }] },
]);
expect(mockGenerateContent).not.toHaveBeenCalled();
});
it('should not do anything if latestTurnContext is empty', () => {
service.triggerMicroConsolidation([]);
expect(mockGenerateContent).not.toHaveBeenCalled();
});
it('should trigger consolidation and append to in-memory hippocampus', async () => {
service.triggerMicroConsolidation([
{ role: 'user', parts: [{ text: 'test' }] },
]);
// Wait a tick for the fire-and-forget promise to resolve
await new Promise((resolve) => setTimeout(resolve, 0));
expect(mockGenerateContent).toHaveBeenCalledWith(
expect.objectContaining({
modelConfigKey: { model: 'gemini-3-flash-preview', isChatModel: false },
systemInstruction: expect.stringContaining(
'subconscious memory module',
),
}),
);
expect(mockAppendHippocampusEntry).toHaveBeenCalledWith(
expect.stringMatching(
/\[\d{2}:\d{2}:\d{2}\] - Mocked consolidated fact\.\n/,
),
);
});
it('should not append entry when model returns NO_SIGNIFICANT_FACTS', async () => {
mockGenerateContent.mockResolvedValue({
text: 'NO_SIGNIFICANT_FACTS',
});
service.triggerMicroConsolidation([
{ role: 'user', parts: [{ text: 'test' }] },
]);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(mockAppendHippocampusEntry).not.toHaveBeenCalled();
});
});