mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(codex): dedupe repeated goal updates (#649)
This commit is contained in:
@@ -41,6 +41,7 @@ const harness = vi.hoisted(() => ({
|
||||
emitSecondChildMessage: false,
|
||||
emitLateChildCommandAfterParentTool: false,
|
||||
emitParentUsageEvents: false,
|
||||
emitParentGoalDuplicateEvents: false,
|
||||
emitChildNestedAgentTool: false,
|
||||
emitParentTitleChange: false,
|
||||
emitParentSpawnFailureWithoutAgentId: false,
|
||||
@@ -223,6 +224,41 @@ vi.mock('./codexAppServerClient', () => {
|
||||
}
|
||||
|
||||
if (params?.threadId === 'thread-1') {
|
||||
if (harness.emitParentGoalDuplicateEvents) {
|
||||
const goalBase = {
|
||||
threadId,
|
||||
objective: 'keep benchmark work moving',
|
||||
status: 'active',
|
||||
tokenBudget: null,
|
||||
tokensUsed: 0,
|
||||
timeUsedSeconds: 0,
|
||||
createdAt: 1
|
||||
};
|
||||
for (let index = 0; index < 4; index += 1) {
|
||||
const notification = {
|
||||
threadId,
|
||||
goal: {
|
||||
...goalBase,
|
||||
timeUsedSeconds: index,
|
||||
updatedAt: 2 + index
|
||||
}
|
||||
};
|
||||
harness.notifications.push({ method: 'thread/goal/updated', params: notification });
|
||||
this.notificationHandler?.('thread/goal/updated', notification);
|
||||
}
|
||||
const pausedNotification = {
|
||||
threadId,
|
||||
goal: {
|
||||
...goalBase,
|
||||
status: 'paused',
|
||||
timeUsedSeconds: 4,
|
||||
updatedAt: 6
|
||||
}
|
||||
};
|
||||
harness.notifications.push({ method: 'thread/goal/updated', params: pausedNotification });
|
||||
this.notificationHandler?.('thread/goal/updated', pausedNotification);
|
||||
}
|
||||
|
||||
if (harness.emitParentTitleChange) {
|
||||
const titleStart = {
|
||||
item: {
|
||||
@@ -928,6 +964,7 @@ describe('codexRemoteLauncher', () => {
|
||||
harness.emitSecondChildMessage = false;
|
||||
harness.emitLateChildCommandAfterParentTool = false;
|
||||
harness.emitParentUsageEvents = false;
|
||||
harness.emitParentGoalDuplicateEvents = false;
|
||||
harness.emitChildNestedAgentTool = false;
|
||||
harness.emitParentTitleChange = false;
|
||||
harness.emitParentSpawnFailureWithoutAgentId = false;
|
||||
@@ -1700,6 +1737,57 @@ describe('codexRemoteLauncher', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('suppresses duplicate parent goal updates that only change runtime counters', async () => {
|
||||
harness.emitParentGoalDuplicateEvents = true;
|
||||
const { session, codexMessages } = createSessionStub();
|
||||
|
||||
await codexRemoteLauncher(session as never);
|
||||
|
||||
const goalMessages = codexMessages.filter((message): message is Record<string, unknown> => {
|
||||
return Boolean(message && typeof message === 'object' && (message as Record<string, unknown>).type === 'thread_goal_updated');
|
||||
});
|
||||
expect(goalMessages).toHaveLength(2);
|
||||
expect(goalMessages).toEqual([
|
||||
expect.objectContaining({
|
||||
thread_id: 'thread-1',
|
||||
goal: expect.objectContaining({
|
||||
status: 'active',
|
||||
updatedAt: 2
|
||||
})
|
||||
}),
|
||||
expect.objectContaining({
|
||||
thread_id: 'thread-1',
|
||||
goal: expect.objectContaining({
|
||||
status: 'paused',
|
||||
updatedAt: 6
|
||||
})
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it('suppresses duplicate goal events from repeated show commands', async () => {
|
||||
const { session, codexMessages } = createSessionStub([
|
||||
'/goal keep benchmark work moving',
|
||||
'/goal'
|
||||
]);
|
||||
|
||||
await codexRemoteLauncher(session as never);
|
||||
|
||||
expect(harness.goalSetCalls).toHaveLength(1);
|
||||
expect(harness.goalGetCalls).toEqual([{ threadId: 'thread-1' }]);
|
||||
const goalMessages = codexMessages.filter((message): message is Record<string, unknown> => {
|
||||
return Boolean(message && typeof message === 'object' && (message as Record<string, unknown>).type === 'thread_goal_updated');
|
||||
});
|
||||
expect(goalMessages).toHaveLength(1);
|
||||
expect(goalMessages[0]).toEqual(expect.objectContaining({
|
||||
thread_id: 'thread-1',
|
||||
goal: expect.objectContaining({
|
||||
objective: 'keep benchmark work moving',
|
||||
status: 'active'
|
||||
})
|
||||
}));
|
||||
});
|
||||
|
||||
it('marks parent usage and compact events with parent scope', async () => {
|
||||
harness.emitParentUsageEvents = true;
|
||||
const { session, codexMessages } = createSessionStub();
|
||||
|
||||
@@ -65,6 +65,40 @@ const SAME_THREAD_COMPACT_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
const CODEX_GOALS_UNSUPPORTED_MESSAGE = 'Codex goals are not supported by this Codex runtime. Upgrade Codex or enable features.goals.';
|
||||
const MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4_000;
|
||||
|
||||
type GoalForwardSignature = {
|
||||
objective: string | null;
|
||||
status: string | null;
|
||||
tokenBudget: number | null;
|
||||
tokenBucket: number | null;
|
||||
};
|
||||
|
||||
function goalNumber(value: unknown): number | null {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function goalString(value: unknown): string | null {
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
|
||||
function buildGoalForwardSignature(goal: Record<string, unknown>): GoalForwardSignature {
|
||||
const tokenBudget = goalNumber(goal.tokenBudget ?? goal.token_budget);
|
||||
const tokensUsed = goalNumber(goal.tokensUsed ?? goal.tokens_used) ?? 0;
|
||||
const tokenBucket = tokenBudget !== null && tokenBudget > 0
|
||||
? Math.floor(Math.min(tokensUsed, tokenBudget) / Math.max(1, tokenBudget * 0.05))
|
||||
: null;
|
||||
|
||||
return {
|
||||
objective: goalString(goal.objective),
|
||||
status: goalString(goal.status),
|
||||
tokenBudget,
|
||||
tokenBucket
|
||||
};
|
||||
}
|
||||
|
||||
function goalForwardSignatureKey(signature: GoalForwardSignature): string {
|
||||
return JSON.stringify(signature);
|
||||
}
|
||||
|
||||
function isSameThreadRetryableCodexError(error: string | null): boolean {
|
||||
if (!error) {
|
||||
return false;
|
||||
@@ -1820,6 +1854,33 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
|
||||
});
|
||||
};
|
||||
|
||||
const forwardedGoalSignaturesByThreadId = new Map<string, string>();
|
||||
|
||||
const shouldForwardGoalUpdate = (msg: Record<string, unknown>, threadId: string | null): boolean => {
|
||||
const goal = asRecord(msg.goal);
|
||||
const scopedThreadId = threadId
|
||||
?? asString(goal?.threadId ?? goal?.thread_id)
|
||||
?? this.currentThreadId;
|
||||
if (!goal || !scopedThreadId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const signature = goalForwardSignatureKey(buildGoalForwardSignature(goal));
|
||||
if (forwardedGoalSignaturesByThreadId.get(scopedThreadId) === signature) {
|
||||
logger.debug(`[Codex] Suppressing duplicate thread goal update; threadId=${scopedThreadId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
forwardedGoalSignaturesByThreadId.set(scopedThreadId, signature);
|
||||
return true;
|
||||
};
|
||||
|
||||
const noteGoalCleared = (threadId: string | null) => {
|
||||
if (threadId) {
|
||||
forwardedGoalSignaturesByThreadId.delete(threadId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCodexEvent = (msg: Record<string, unknown>) => {
|
||||
const msgType = asString(msg.type);
|
||||
if (!msgType) return;
|
||||
@@ -1876,14 +1937,17 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
|
||||
}
|
||||
|
||||
if (msgType === 'thread_goal_updated') {
|
||||
session.sendAgentMessage({
|
||||
...addCodexEventScope(msg, 'parent', eventThreadId ?? this.currentThreadId),
|
||||
id: randomUUID()
|
||||
});
|
||||
if (shouldForwardGoalUpdate(msg, eventThreadId)) {
|
||||
session.sendAgentMessage({
|
||||
...addCodexEventScope(msg, 'parent', eventThreadId ?? this.currentThreadId),
|
||||
id: randomUUID()
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msgType === 'thread_goal_cleared') {
|
||||
noteGoalCleared(eventThreadId ?? this.currentThreadId);
|
||||
session.sendAgentMessage({
|
||||
...addCodexEventScope(msg, 'parent', eventThreadId ?? this.currentThreadId),
|
||||
id: randomUUID()
|
||||
@@ -2461,6 +2525,12 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
|
||||
};
|
||||
|
||||
const sendGoalEvent = (event: Record<string, unknown>) => {
|
||||
const threadId = asString(event.thread_id ?? event.threadId) ?? this.currentThreadId;
|
||||
if (event.type === 'thread_goal_cleared') {
|
||||
noteGoalCleared(threadId);
|
||||
} else if (event.type === 'thread_goal_updated' && !shouldForwardGoalUpdate(event, threadId)) {
|
||||
return;
|
||||
}
|
||||
session.sendAgentMessage({
|
||||
...addCodexEventScope(event, 'parent', this.currentThreadId),
|
||||
id: randomUUID()
|
||||
|
||||
Reference in New Issue
Block a user