From 8705d502f087967042aac59272cefcfc737fa500 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Sat, 4 Jul 2026 17:03:45 +0800 Subject: [PATCH] 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 --- .../agent-core-v2/src/agent/goal/goalService.ts | 2 +- .../src/agent/prompt/promptService.ts | 10 ++++------ packages/agent-core-v2/src/agent/turn/turn.ts | 3 +-- .../agent-core-v2/src/agent/turn/turnService.ts | 10 +++------- .../agent-core-v2/test/gateway/gateway.test.ts | 2 +- packages/agent-core-v2/test/goal/goal.test.ts | 6 +++--- .../test/prompt/promptService.test.ts | 4 ++-- packages/agent-core-v2/test/turn/stubs.ts | 16 ++++++++-------- .../agent-core-v2/test/turn/turn-ready.test.ts | 11 ++++------- 9 files changed, 27 insertions(+), 37 deletions(-) diff --git a/packages/agent-core-v2/src/agent/goal/goalService.ts b/packages/agent-core-v2/src/agent/goal/goalService.ts index 6a7eb7b53..f57a9dd25 100644 --- a/packages/agent-core-v2/src/agent/goal/goalService.ts +++ b/packages/agent-core-v2/src/agent/goal/goalService.ts @@ -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 { diff --git a/packages/agent-core-v2/src/agent/prompt/promptService.ts b/packages/agent-core-v2/src/agent/prompt/promptService.ts index 8cdacf81e..45da94144 100644 --- a/packages/agent-core-v2/src/agent/prompt/promptService.ts +++ b/packages/agent-core-v2/src/agent/prompt/promptService.ts @@ -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; } diff --git a/packages/agent-core-v2/src/agent/turn/turn.ts b/packages/agent-core-v2/src/agent/turn/turn.ts index f8216c694..455e94135 100644 --- a/packages/agent-core-v2/src/agent/turn/turn.ts +++ b/packages/agent-core-v2/src/agent/turn/turn.ts @@ -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<{ diff --git a/packages/agent-core-v2/src/agent/turn/turnService.ts b/packages/agent-core-v2/src/agent/turn/turnService.ts index 6d79059ba..082a5c0c6 100644 --- a/packages/agent-core-v2/src/agent/turn/turnService.ts +++ b/packages/agent-core-v2/src/agent/turn/turnService.ts @@ -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(); @@ -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>, ): Promise { 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, diff --git a/packages/agent-core-v2/test/gateway/gateway.test.ts b/packages/agent-core-v2/test/gateway/gateway.test.ts index 5c0071342..895711e40 100644 --- a/packages/agent-core-v2/test/gateway/gateway.test.ts +++ b/packages/agent-core-v2/test/gateway/gateway.test.ts @@ -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); diff --git a/packages/agent-core-v2/test/goal/goal.test.ts b/packages/agent-core-v2/test/goal/goal.test.ts index 64d9e9c47..d3f675555 100644 --- a/packages/agent-core-v2/test/goal/goal.test.ts +++ b/packages/agent-core-v2/test/goal/goal.test.ts @@ -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 () => { diff --git a/packages/agent-core-v2/test/prompt/promptService.test.ts b/packages/agent-core-v2/test/prompt/promptService.test.ts index bb25db1e8..cc7831eab 100644 --- a/packages/agent-core-v2/test/prompt/promptService.test.ts +++ b/packages/agent-core-v2/test/prompt/promptService.test.ts @@ -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 & { 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; diff --git a/packages/agent-core-v2/test/turn/stubs.ts b/packages/agent-core-v2/test/turn/stubs.ts index 269b15a37..f768aac34 100644 --- a/packages/agent-core-v2/test/turn/stubs.ts +++ b/packages/agent-core-v2/test/turn/stubs.ts @@ -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; diff --git a/packages/agent-core-v2/test/turn/turn-ready.test.ts b/packages/agent-core-v2/test/turn/turn-ready.test.ts index 64be03eb8..a490b3c53 100644 --- a/packages/agent-core-v2/test/turn/turn-ready.test.ts +++ b/packages/agent-core-v2/test/turn/turn-ready.test.ts @@ -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 {