mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
fix(codex): announce automatic context compaction when it starts (#1999)
This commit is contained in:
@@ -106,4 +106,45 @@ describe('shared history projection', () => {
|
||||
await projection.notification('item/completed', { threadId: 'child', turnId: 'turn', item: { id: 'prompt', type: 'userMessage', content: [{ type: 'text', text: 'child prompt' }], clientId: 'cid' } });
|
||||
expect(user).not.toHaveBeenCalled(); expect(committed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('announces parent compaction start via session event, deduping repeats', async () => {
|
||||
const send = vi.fn(); const sendEvent = vi.fn();
|
||||
const session = { getMetadata: () => ({}), sendAgentMessage: send, sendSessionEvent: sendEvent } as unknown as ApiSessionClient;
|
||||
const projection = new SharedCodexProjection(session, 'thread', async () => {});
|
||||
const params = { threadId: 'thread', turnId: 'turn', item: { id: 'c1', type: 'contextCompaction' } };
|
||||
await projection.notification('item/started', params);
|
||||
await projection.notification('item/started', params);
|
||||
await projection.notification('item/started', { threadId: 'thread', turnId: 'turn', item: { id: 'c2', type: 'contextCompaction' } });
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
expect(sendEvent.mock.calls).toEqual([
|
||||
[{ type: 'message', message: '📦 Compaction started' }],
|
||||
[{ type: 'message', message: '📦 Compaction started' }]
|
||||
]);
|
||||
});
|
||||
it('keeps child compaction start as trace with statusText, never a parent status', async () => {
|
||||
const send = vi.fn(); const sendEvent = vi.fn();
|
||||
const session = { getMetadata: () => ({}), sendAgentMessage: send, sendSessionEvent: sendEvent } as unknown as ApiSessionClient;
|
||||
const projection = new SharedCodexProjection(session, 'child', async () => {}, 'root');
|
||||
await projection.notification('item/started', { threadId: 'child', turnId: 'turn', item: { id: 'c1', type: 'contextCompaction' } });
|
||||
expect(sendEvent).not.toHaveBeenCalled();
|
||||
expect(send).toHaveBeenCalledTimes(1);
|
||||
const [body] = send.mock.calls[0];
|
||||
expect(body.type).toBe('agent-run-trace');
|
||||
expect(body.message.type).toBe('compaction_started');
|
||||
expect(body.message.statusText).toBe('📦 Compaction started');
|
||||
});
|
||||
it('emits live compaction start but not for completed history replay', async () => {
|
||||
const send = vi.fn(); const sendEvent = vi.fn();
|
||||
const session = { getMetadata: () => ({}), sendAgentMessage: send, sendSessionEvent: sendEvent } as unknown as ApiSessionClient;
|
||||
const projection = new SharedCodexProjection(session, 'thread', async () => {});
|
||||
await projection.history({ turns: [{ id: 'turn', status: 'completed',
|
||||
items: [{ id: 'c1', type: 'contextCompaction' }] }] });
|
||||
expect(sendEvent).not.toHaveBeenCalled();
|
||||
expect(send.mock.calls.map(([b]) => b).some((b) => b.message === '📦 Compaction started')).toBe(false);
|
||||
expect(send.mock.calls.map(([b]) => b)).toContainEqual(expect.objectContaining({ type: 'context_compacted' }));
|
||||
send.mockClear();
|
||||
await projection.notification('item/started', { threadId: 'thread', turnId: 'live', item: { id: 'c9', type: 'contextCompaction' } });
|
||||
expect(send).not.toHaveBeenCalled();
|
||||
expect(sendEvent).toHaveBeenCalledWith({ type: 'message', message: '📦 Compaction started' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ export class SharedCodexProjection {
|
||||
await this.project(method, params, modelAtReceipt);
|
||||
}
|
||||
|
||||
private async project(method: string, params: unknown, modelAtReceipt?: string): Promise<void> {
|
||||
private async project(method: string, params: unknown, modelAtReceipt?: string, fromHistory = false): Promise<void> {
|
||||
if (method.startsWith('codex/event/')) return;
|
||||
const p = record(params);
|
||||
const item = record(p.item);
|
||||
@@ -146,6 +146,20 @@ export class SharedCodexProjection {
|
||||
this.send({ type: 'tool-call', name: event.name, callId, input: event.input ?? event.arguments }, key);
|
||||
} else if (event.type === 'codex_tool_call_end' && callId) {
|
||||
this.send({ type: 'tool-call-result', callId, output: event.output, is_error: event.is_error }, key);
|
||||
} else if (event.type === 'compaction_started') {
|
||||
// History replay re-projects every completed item's start;
|
||||
// a finished compaction must not announce a live status.
|
||||
if (fromHistory) continue;
|
||||
if (this.parentThreadId) {
|
||||
this.send({ type: 'compaction_started', statusText: '📦 Compaction started' }, key);
|
||||
} else {
|
||||
// Status UI listens on session events; an agent message
|
||||
// would render as a normal assistant bubble instead.
|
||||
// Dedup is in-memory only (this.emitted set).
|
||||
if (this.emitted.has(key)) continue;
|
||||
this.emitted.add(key);
|
||||
this.session.sendSessionEvent({ type: 'message', message: '📦 Compaction started' });
|
||||
}
|
||||
} else if (event.type === 'token_count' || event.type === 'context_compacted' || event.type.startsWith('thread_goal_')) {
|
||||
const model = event.type === 'token_count' && turnId ? this.turnModels.get(turnId) : undefined;
|
||||
this.send({ ...event, ...(model ? { model } : {}), flavor: 'codex', scope: { role: 'parent', threadId: this.threadId }, scope_role: 'parent', thread_id: this.threadId }, key);
|
||||
@@ -191,7 +205,7 @@ export class SharedCodexProjection {
|
||||
if (!this.parentThreadId && string(record(item).id) && pendingTitle && !this.completedTitles.has(titleKey)) {
|
||||
this.pendingTitles.set(titleKey, pendingTitle);
|
||||
}
|
||||
await this.project('item/started', params);
|
||||
await this.project('item/started', params, undefined, true);
|
||||
// Active snapshots can contain partial assistant text. Do not
|
||||
// settle it under the final stable id and suppress completion.
|
||||
if (turn.status !== 'inProgress' || record(item).status === 'completed' || record(item).type === 'userMessage') {
|
||||
@@ -203,7 +217,7 @@ export class SharedCodexProjection {
|
||||
}
|
||||
this.pendingTitles.delete(titleKey);
|
||||
}
|
||||
await this.project('item/completed', params);
|
||||
await this.project('item/completed', params, undefined, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user