Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions backend/__tests__/unit/services/agentEventService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('AgentEventService', () => {
beforeEach(() => {
jest.clearAllMocks();
delete process.env.AGENT_CONTEXT_OVERFLOW_RETRY_LIMIT;
delete process.env.COMMONLY_CCP_ENABLED;
});

test('acknowledge auto-recovers OpenClaw context overflow and re-enqueues once', async () => {
Expand Down Expand Up @@ -213,6 +214,11 @@ describe('AgentEventService', () => {
cyclesDigest: [{ ts: new Date(), content: 'c' }],
longTermDigest: 'durable',
});
AgentInstallation.findOne.mockReturnValue({
select: () => ({
lean: jest.fn().mockResolvedValue(null),
}),
});

AgentEvent.findOneAndUpdate.mockReturnValue({
lean: jest.fn().mockResolvedValue({
Expand Down Expand Up @@ -262,6 +268,81 @@ describe('AgentEventService', () => {
cyclesDigest: expect.any(Array),
longTermDigest: 'durable',
}));
expect(events[0].continuity).toBeUndefined();
});

test('list includes continuity when CCP emission is enabled', async () => {
const AgentMemory = require('../../../models/AgentMemory');
const { buildMemoryDigestBundle } = require('../../../services/agentMemoryService');
process.env.COMMONLY_CCP_ENABLED = '1';

AgentEvent.find.mockReturnValue({
sort: () => ({
limit: () => ({
select: () => ({
lean: jest.fn().mockResolvedValue([{ _id: 'evt-1' }]),
}),
}),
}),
});

AgentMemory.findOne.mockReturnValue({
select: () => ({
lean: jest.fn().mockResolvedValue({
revision: 7,
lastSeenRevision: 5,
sections: { /* shape-only */ },
}),
}),
});

buildMemoryDigestBundle.mockReturnValue({
memoryRevision: 7,
memoryDigest: [{ takeaway: 'a' }, { takeaway: 'b' }],
cyclesDigest: [{ ts: new Date(), content: 'c' }],
longTermDigest: 'durable',
});

AgentEvent.findOneAndUpdate.mockReturnValue({
lean: jest.fn().mockResolvedValue({
_id: 'evt-1',
agentName: 'openclaw',
instanceId: 'liz',
podId: 'pod-1',
type: 'chat.mention',
status: 'delivered',
memoryRevisionAtDelivery: 7,
payload: { messageId: 1800, content: 'hi' },
}),
});

const events = await AgentEventService.list({
agentName: 'openclaw',
instanceId: 'liz',
podId: 'pod-1',
limit: 5,
});

expect(events).toHaveLength(1);
expect(events[0].continuity).toEqual(expect.objectContaining({
schema: 'commonly.ccp.v1',
contextId: 'cap-event:evt-1',
owner: {
agentName: 'openclaw',
instanceId: 'liz',
podId: 'pod-1',
},
freshness: {
memoryRevision: 7,
memoryRevisionAtDelivery: 7,
lastSeenRevision: 5,
status: 'stale',
},
refs: expect.objectContaining({
messageId: '1800',
memorySections: ['system_exchanges', 'cycles', 'long_term'],
}),
}));
});

test('list returns [] when no candidates are pending (no envelope read)', async () => {
Expand Down Expand Up @@ -309,6 +390,11 @@ describe('AgentEventService', () => {
}),
});
buildMemoryDigestBundle.mockReturnValue({});
AgentInstallation.findOne.mockReturnValue({
select: () => ({
lean: jest.fn().mockResolvedValue(null),
}),
});

AgentEvent.findOneAndUpdate
.mockReturnValueOnce({
Expand Down
187 changes: 187 additions & 0 deletions backend/__tests__/unit/services/contextContinuityPacketService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// @ts-nocheck

const {
CONTEXT_CONTINUITY_PACKET_SCHEMA,
buildContextContinuityPacket,
memorySectionsFromDigestBundle,
} = require('../../../services/contextContinuityPacketService');

describe('contextContinuityPacketService', () => {
it('builds the stable commonly.ccp.v1 envelope', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-1',
type: 'chat.mention',
agentName: 'OpenClaw',
instanceId: 'liz',
podId: 'pod-1',
createdAt: new Date('2026-06-24T00:00:00Z'),
deliveredAt: new Date('2026-06-24T00:00:03Z'),
payload: { trigger: 'mention', messageId: 42 },
},
memoryRevision: 7,
memoryRevisionAtDelivery: 7,
lastSeenRevision: 5,
memorySections: ['system_exchanges'],
});

expect(packet).toEqual({
schema: CONTEXT_CONTINUITY_PACKET_SCHEMA,
contextId: 'cap-event:evt-1',
owner: {
agentName: 'openclaw',
instanceId: 'liz',
podId: 'pod-1',
},
provenance: {
source: 'cap.event',
eventId: 'evt-1',
eventType: 'chat.mention',
trigger: 'mention',
createdAt: '2026-06-24T00:00:00.000Z',
deliveredAt: '2026-06-24T00:00:03.000Z',
},
freshness: {
memoryRevision: 7,
memoryRevisionAtDelivery: 7,
lastSeenRevision: 5,
status: 'stale',
},
refs: {
messageId: '42',
memorySections: ['system_exchanges'],
},
});
});

it('extracts safe payload refs without copying content or memory bodies', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-2',
type: 'agent.ask',
agentName: 'codex',
instanceId: 'default',
podId: 'pod-2',
payload: {
content: 'do not copy me',
longTermDigest: 'do not copy memory',
memoryDigest: [{ takeaway: 'do not copy digest entry' }],
requestId: 'ask-1',
taskId: 'task-1',
threadId: 'thread-1',
replyToMessageId: 'msg-parent',
summaryId: 'sum-1',
integrationId: 'int-1',
},
},
});

expect(packet.refs).toEqual({
replyToMessageId: 'msg-parent',
threadId: 'thread-1',
taskId: 'task-1',
requestId: 'ask-1',
summaryId: 'sum-1',
integrationId: 'int-1',
});
expect(JSON.stringify(packet)).not.toContain('do not copy me');
expect(JSON.stringify(packet)).not.toContain('do not copy memory');
expect(JSON.stringify(packet)).not.toContain('do not copy digest entry');
});

it('marks freshness stale when the delivery snapshot is behind current memory', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-3',
agentName: 'openclaw',
instanceId: 'pixel',
podId: 'pod-3',
payload: {},
},
memoryRevision: 9,
memoryRevisionAtDelivery: 7,
lastSeenRevision: 6,
});

expect(packet.freshness).toEqual({
memoryRevision: 9,
memoryRevisionAtDelivery: 7,
lastSeenRevision: 6,
status: 'stale',
});
});

it('marks freshness valid when the agent has seen the current memory revision', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-valid',
agentName: 'openclaw',
instanceId: 'pixel',
podId: 'pod-valid',
payload: {},
},
memoryRevision: 9,
memoryRevisionAtDelivery: 9,
lastSeenRevision: 9,
});

expect(packet.freshness).toEqual({
memoryRevision: 9,
memoryRevisionAtDelivery: 9,
lastSeenRevision: 9,
status: 'valid',
});
});

it('marks freshness unknown when lastSeenRevision is missing', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-unknown',
agentName: 'openclaw',
instanceId: 'pixel',
podId: 'pod-unknown',
payload: {},
},
memoryRevision: 9,
memoryRevisionAtDelivery: 9,
});

expect(packet.freshness).toEqual({
memoryRevision: 9,
memoryRevisionAtDelivery: 9,
status: 'unknown',
});
});

it('omits freshness when no memory snapshot is supplied', () => {
const packet = buildContextContinuityPacket({
event: {
_id: 'evt-4',
type: 'heartbeat',
agentName: 'openclaw',
instanceId: 'default',
podId: 'pod-4',
payload: {},
},
});

expect(packet.owner).toEqual({
agentName: 'openclaw',
instanceId: 'default',
podId: 'pod-4',
});
expect(packet.freshness).toBeUndefined();
expect(packet.refs).toBeUndefined();
});

it('deduplicates memory section refs from digest bundle presence', () => {
const sections = memorySectionsFromDigestBundle({
memoryDigest: [{ takeaway: 'x' }],
cyclesDigest: [{ content: 'cycle' }],
longTermDigest: 'memory',
recentDailyDigest: [{ date: '2026-06-24', content: 'daily' }],
});

expect(sections).toEqual(['system_exchanges', 'cycles', 'long_term', 'daily']);
});
});
Loading
Loading