mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
refactor: remove PromptOrigin from IAgentTurnService.launch
Turn origin is tracked on context messages instead; drop it from the turn launch API and wire records. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0d8b9f96ef
commit
8705d502f0
9 changed files with 27 additions and 37 deletions
|
|
@ -504,7 +504,7 @@ export class AgentGoalService extends Disposable implements IAgentGoalService {
|
|||
origin: GOAL_CONTINUATION_ORIGIN,
|
||||
});
|
||||
this.context.splice(this.context.get().length, 0, [message]);
|
||||
this.turnService.launch(GOAL_CONTINUATION_ORIGIN);
|
||||
this.turnService.launch();
|
||||
}
|
||||
|
||||
private normalizeAfterReplay(): void {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import { ErrorCodes, KimiError } from '#/errors';
|
|||
import {
|
||||
ensureMessageId,
|
||||
IAgentContextMemoryService,
|
||||
USER_PROMPT_ORIGIN,
|
||||
type ContextMessage,
|
||||
type PromptOrigin,
|
||||
} from '#/agent/contextMemory';
|
||||
import { IAgentLoopService } from '#/agent/loop';
|
||||
import { IAgentRecordService } from '#/agent/record';
|
||||
|
|
@ -56,7 +54,7 @@ export class AgentPromptService implements IAgentPromptService {
|
|||
const stamped = ensureMessageId(message);
|
||||
this.append(stamped);
|
||||
if (await this.blockedByHook(stamped, false)) return undefined;
|
||||
return this.launch(stamped.origin ?? USER_PROMPT_ORIGIN);
|
||||
return this.launch();
|
||||
}
|
||||
|
||||
steer(message: ContextMessage): PromptSteerHandle {
|
||||
|
|
@ -82,7 +80,7 @@ export class AgentPromptService implements IAgentPromptService {
|
|||
}
|
||||
|
||||
retry(trigger?: string): Turn | undefined {
|
||||
return this.launch({ kind: 'retry', trigger });
|
||||
return this.launch();
|
||||
}
|
||||
|
||||
undo(count: number): number {
|
||||
|
|
@ -134,8 +132,8 @@ export class AgentPromptService implements IAgentPromptService {
|
|||
this.context.splice(this.context.get().length, 0, messages);
|
||||
}
|
||||
|
||||
private launch(origin: PromptOrigin): Turn {
|
||||
const turn = this.turnService.launch(origin);
|
||||
private launch(): Turn {
|
||||
const turn = this.turnService.launch();
|
||||
this.observe(turn);
|
||||
return turn;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { createDecorator } from "#/_base/di";
|
||||
import type { TurnResult } from '#/agent/loop';
|
||||
import type { PromptOrigin } from '#/agent/contextMemory';
|
||||
import type { Hooks } from '#/hooks';
|
||||
|
||||
export type { TurnResult } from '#/agent/loop';
|
||||
|
|
@ -23,7 +22,7 @@ export interface TurnEndedContext {
|
|||
|
||||
export interface IAgentTurnService {
|
||||
readonly _serviceBrand: undefined;
|
||||
launch(origin: PromptOrigin): Turn;
|
||||
launch(): Turn;
|
||||
getActiveTurn(): Turn | undefined;
|
||||
|
||||
readonly hooks: Hooks<{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { createControlledPromise } from '@antfu/utils';
|
|||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { ErrorCodes, KimiError, toKimiErrorPayload } from '#/errors';
|
||||
import type { PromptOrigin } from '#/agent/contextMemory';
|
||||
import { OrderedHookSlot } from '#/hooks';
|
||||
import { IAgentLoopService } from '#/agent/loop';
|
||||
import { IAgentTelemetryContextService, ITelemetryService } from '#/app/telemetry';
|
||||
|
|
@ -19,7 +18,6 @@ declare module '#/agent/wireRecord' {
|
|||
interface WireRecordMap {
|
||||
'turn.launch': {
|
||||
turnId: number;
|
||||
origin: PromptOrigin;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +45,7 @@ export class AgentTurnService implements IAgentTurnService {
|
|||
});
|
||||
}
|
||||
|
||||
launch(origin: PromptOrigin): Turn {
|
||||
launch(): Turn {
|
||||
if (this.activeTurn !== undefined) {
|
||||
throw new KimiError(
|
||||
ErrorCodes.TURN_AGENT_BUSY,
|
||||
|
|
@ -57,7 +55,7 @@ export class AgentTurnService implements IAgentTurnService {
|
|||
}
|
||||
|
||||
const turnId = this.nextTurnId;
|
||||
this.record.append({ type: 'turn.launch', turnId, origin });
|
||||
this.record.append({ type: 'turn.launch', turnId });
|
||||
this.restoreLaunch(turnId);
|
||||
const abortController = new AbortController();
|
||||
const ready = createControlledPromise<void>();
|
||||
|
|
@ -69,7 +67,7 @@ export class AgentTurnService implements IAgentTurnService {
|
|||
};
|
||||
void ready.catch(() => undefined);
|
||||
this.activeTurn = turn;
|
||||
turn.result = this.runTurn(turn, origin, ready);
|
||||
turn.result = this.runTurn(turn, ready);
|
||||
void this.hooks.onLaunched.run({ turn });
|
||||
return turn;
|
||||
}
|
||||
|
|
@ -80,7 +78,6 @@ export class AgentTurnService implements IAgentTurnService {
|
|||
|
||||
private async runTurn(
|
||||
turn: Turn,
|
||||
origin: PromptOrigin,
|
||||
ready: ReturnType<typeof createControlledPromise<void>>,
|
||||
): Promise<TurnResult> {
|
||||
const startedAt = Date.now();
|
||||
|
|
@ -91,7 +88,6 @@ export class AgentTurnService implements IAgentTurnService {
|
|||
this.record.signal({
|
||||
type: 'turn.started',
|
||||
turnId: turn.id,
|
||||
origin,
|
||||
});
|
||||
result = await this.loop.runTurn(turn.id, {
|
||||
signal: turn.abortController.signal,
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ describe('RestGateway', () => {
|
|||
|
||||
it('aborts the active turn signal on cancel', async () => {
|
||||
const gw = ix.get(IRestGateway);
|
||||
const turn = turnService.launch({ kind: 'user' });
|
||||
const turn = turnService.launch();
|
||||
await gw.cancel('s1', 'main', 'bye');
|
||||
|
||||
expect(turn.abortController.signal.aborted).toBe(true);
|
||||
|
|
|
|||
|
|
@ -567,7 +567,7 @@ describe('AgentGoalService core workflow hooks', () => {
|
|||
status: 'active',
|
||||
turnsUsed: 1,
|
||||
});
|
||||
expect(turnService.launches).toEqual([{ kind: 'system_trigger', name: 'goal_continuation' }]);
|
||||
expect(turnService.launches).toHaveLength(1);
|
||||
expect(context.get().at(-1)?.origin).toEqual({
|
||||
kind: 'system_trigger',
|
||||
name: 'goal_continuation',
|
||||
|
|
@ -596,7 +596,7 @@ describe('AgentGoalService core workflow hooks', () => {
|
|||
await goals.createGoal({ objective: 'finish the task' });
|
||||
await goals.setBudgetLimits({ budgetLimits: { tokenBudget: 7 } }, 'model');
|
||||
|
||||
const turn = turnService.launch({ kind: 'user' });
|
||||
const turn = turnService.launch();
|
||||
await turnService.hooks.onLaunched.run({ turn });
|
||||
|
||||
expect(
|
||||
|
|
@ -653,7 +653,7 @@ describe('AgentGoalService core workflow hooks', () => {
|
|||
status: 'active',
|
||||
turnsUsed: 0,
|
||||
});
|
||||
expect(turnService.launches).toEqual([{ kind: 'system_trigger', name: 'goal_continuation' }]);
|
||||
expect(turnService.launches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('requests one final outcome turn after model completion', async () => {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ describe('AgentPromptService', () => {
|
|||
|
||||
it('runs submit hooks before queuing active steers', async () => {
|
||||
const { context, loop, prompt, turn } = createHarness({ hasActiveTurn: true });
|
||||
const activeTurn = turn.launch({ kind: 'user' });
|
||||
const activeTurn = turn.launch();
|
||||
const seen: Array<Pick<PromptSubmitContext, 'isSteer'> & {
|
||||
readonly originKind: string | undefined;
|
||||
}> = [];
|
||||
|
|
@ -123,7 +123,7 @@ describe('AgentPromptService', () => {
|
|||
|
||||
it('does not queue active steers blocked by hooks', async () => {
|
||||
const { context, loop, prompt, turn } = createHarness({ hasActiveTurn: true });
|
||||
const activeTurn = turn.launch({ kind: 'user' });
|
||||
const activeTurn = turn.launch();
|
||||
|
||||
prompt.hooks.onWillSubmitPrompt.register('block', async (ctx) => {
|
||||
ctx.block = true;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
* production tree. Import from a relative path (`./stubs` or `../turn/stubs`).
|
||||
*/
|
||||
|
||||
import type { PromptOrigin } from '#/agent/contextMemory';
|
||||
import type { IAgentLoopService } from '#/agent/loop';
|
||||
import type { IAgentToolExecutorService } from '#/agent/toolExecutor';
|
||||
import type { IAgentTurnService, Turn } from '#/agent/turn';
|
||||
|
|
@ -20,7 +19,7 @@ export interface StubTurnOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* An `IAgentTurnService` stub that also records `launch` origins and exposes the
|
||||
* An `IAgentTurnService` stub that also records `launch` calls and exposes the
|
||||
* active turn. `prompts` / `steered` are retained as empty arrays for legacy
|
||||
* assertions — the HEAD `IAgentTurnService` has no `prompt` / `steer` methods, so
|
||||
* tests that used to drive them should be rewritten against `launch` + hooks.
|
||||
|
|
@ -28,7 +27,7 @@ export interface StubTurnOptions {
|
|||
export type StubTurn = IAgentTurnService & {
|
||||
readonly prompts: readonly string[];
|
||||
readonly steered: readonly string[];
|
||||
readonly launches: readonly PromptOrigin[];
|
||||
readonly launches: readonly number[];
|
||||
};
|
||||
|
||||
function makeTurn(id: number): Turn {
|
||||
|
|
@ -57,16 +56,17 @@ function makeAgentLoopHookSlots(): IAgentLoopService['hooks'] {
|
|||
|
||||
/** A configurable `IAgentTurnService` stub backed by real `OrderedHookSlot`s. */
|
||||
export function stubTurn(options: StubTurnOptions = {}): StubTurn {
|
||||
const launches: PromptOrigin[] = [];
|
||||
const launches: number[] = [];
|
||||
let activeTurn: Turn | undefined;
|
||||
let nextId = typeof options.currentId === 'number' ? options.currentId : 0;
|
||||
return {
|
||||
_serviceBrand: undefined,
|
||||
hooks: makeHooks(),
|
||||
launch(origin) {
|
||||
launches.push(origin);
|
||||
activeTurn = makeTurn(nextId++);
|
||||
return activeTurn;
|
||||
launch() {
|
||||
const turn = makeTurn(nextId++);
|
||||
launches.push(turn.id);
|
||||
activeTurn = turn;
|
||||
return turn;
|
||||
},
|
||||
getActiveTurn() {
|
||||
return options.hasActiveTurn ? activeTurn : undefined;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
|
||||
import { DisposableStore } from '#/_base/di/lifecycle';
|
||||
import { createServices, type TestInstantiationService } from '#/_base/di/test';
|
||||
import type { PromptOrigin } from '#/agent/contextMemory';
|
||||
import { IAgentContextMemoryService } from '#/agent/contextMemory';
|
||||
import { AgentLoopService, IAgentLoopService } from '#/agent/loop';
|
||||
import { IAgentLLMRequesterService } from '#/agent/llmRequester';
|
||||
|
|
@ -21,8 +20,6 @@ import { stubLog } from '../log/stubs';
|
|||
import { recordingTelemetry } from '../telemetry/stubs';
|
||||
import { stubLoopWithHooks, stubToolExecutor } from './stubs';
|
||||
|
||||
const SYSTEM_ORIGIN: PromptOrigin = { kind: 'system_trigger', name: 'test' };
|
||||
|
||||
describe('AgentTurnService ready', () => {
|
||||
let disposables: DisposableStore;
|
||||
let ix: TestInstantiationService;
|
||||
|
|
@ -75,7 +72,7 @@ describe('AgentTurnService ready', () => {
|
|||
return { reason: 'completed', steps: 1 };
|
||||
};
|
||||
|
||||
const turn = ix.get(IAgentTurnService).launch(SYSTEM_ORIGIN);
|
||||
const turn = ix.get(IAgentTurnService).launch();
|
||||
void turn.ready.then(
|
||||
() => {
|
||||
readySettled = true;
|
||||
|
|
@ -103,7 +100,7 @@ describe('AgentTurnService ready', () => {
|
|||
const cause = new Error('loop failed before first step');
|
||||
loop.runTurn = async () => ({ reason: 'failed', error: cause, steps: 0 });
|
||||
|
||||
const turn = ix.get(IAgentTurnService).launch(SYSTEM_ORIGIN);
|
||||
const turn = ix.get(IAgentTurnService).launch();
|
||||
let readyError: unknown;
|
||||
await turn.ready.catch((error: unknown) => {
|
||||
readyError = error;
|
||||
|
|
@ -123,10 +120,10 @@ describe('AgentTurnService ready', () => {
|
|||
};
|
||||
|
||||
const turnService = ix.get(IAgentTurnService);
|
||||
const turn = turnService.launch(SYSTEM_ORIGIN);
|
||||
const turn = turnService.launch();
|
||||
let error: unknown;
|
||||
try {
|
||||
turnService.launch(SYSTEM_ORIGIN);
|
||||
turnService.launch();
|
||||
} catch (caught) {
|
||||
error = caught;
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue