fix: count goal creation turn (#1477)

This commit is contained in:
Luyu Cheng 2026-07-07 21:51:29 +08:00 committed by GitHub
parent 474ce289dd
commit 150206a6f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---
Count the turn that starts an autonomous goal toward its goal turn usage.

View file

@ -370,6 +370,13 @@ export class TurnFlow {
end.event.reason !== 'failed' &&
end.event.reason !== 'filtered'
) {
// The ordinary turn created or resumed the goal, so it counts as the
// first active goal turn before the continuation driver takes over.
const countedGoal = await this.agent.goal.incrementTurn();
if (countedGoal?.budget.overBudget === true) {
await this.agent.goal.markBlocked({ reason: 'A configured budget was reached' });
return end;
}
return await this.driveGoal(
this.allocateTurnId(),
[{ type: 'text', text: GOAL_CONTINUATION_PROMPT }],

View file

@ -291,9 +291,53 @@ describe('goal session end-to-end', () => {
);
const turnStarts = events.filter((e) => e['type'] === 'turn.started').length;
expect(turnStarts).toBeGreaterThanOrEqual(2);
const completionEvent = events.find((event) => {
const change = event['change'] as Record<string, unknown> | undefined;
return event['type'] === 'goal.updated' && change?.['kind'] === 'completion';
});
expect(completionEvent?.['change']).toMatchObject({
kind: 'completion',
stats: expect.objectContaining({ turnsUsed: 2 }),
});
expect((await api.getGoal({ agentId: 'main' })).goal).toBeNull();
});
it('does not start a synthetic continuation when creating the goal exhausts its turn budget', async () => {
const sessionDir = await makeTempDir();
const events: Array<Record<string, unknown>> = [];
const { session, agent, scripted } = await setupSession(sessionDir, events, [
'CreateGoal',
'SetGoalBudget',
]);
const api = new SessionAPIImpl(session);
scripted.mockNextResponse({
type: 'function',
id: 'create',
name: 'CreateGoal',
arguments: JSON.stringify({ objective: 'work' }),
});
scripted.mockNextResponse({
type: 'function',
id: 'budget',
name: 'SetGoalBudget',
arguments: JSON.stringify({ value: 1, unit: 'turns' }),
});
scripted.mockNextResponse({ type: 'text', text: 'Goal created with a one-turn budget.' });
agent.turn.prompt([{ type: 'text', text: 'Please start a one-turn goal' }]);
await agent.turn.waitForCurrentTurn();
const goal = (await api.getGoal({ agentId: 'main' })).goal;
expect(goal?.status).toBe('blocked');
expect(goal?.turnsUsed).toBe(1);
expect(scripted.calls).toHaveLength(3);
expect(events.filter((e) => e['type'] === 'turn.started')).toHaveLength(1);
expect(JSON.stringify(agent.context.history)).not.toContain(
'Continue working toward the active goal',
);
});
it('keeps the active turn alive (cancelable) while driving a goal created mid-turn', async () => {
const sessionDir = await makeTempDir();
const events: Array<Record<string, unknown>> = [];