fix: stopHookContinuationUsed

This commit is contained in:
_Kerman 2026-07-03 12:01:48 +08:00
parent 9a6a722d14
commit 3381c44b16
3 changed files with 19 additions and 22 deletions

View file

@ -77,6 +77,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter
declare readonly _serviceBrand: undefined;
private dynamicEngine: HookEngine | undefined;
private stopHookContinuationUsed = false;
constructor(
private readonly options: ExternalHooksServiceOptions = {},
@ -203,6 +204,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter
loop.hooks.onWillStop.register('externalHooks', async (ctx, next) => {
const reason = await this.runStop(ctx);
if (reason !== undefined) {
this.stopHookContinuationUsed = true;
ctx.continuationPrompt = reason;
return;
}
@ -314,6 +316,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter
}
private notifyTurnEnded(ctx: TurnEndedContext): void {
this.stopHookContinuationUsed = false;
if (ctx.result.reason === 'failed' && ctx.result.error !== undefined) {
this.notifyStopFailure(ctx.result.error, ctx.turn.abortController.signal);
}
@ -343,9 +346,11 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter
private async runStop(ctx: TurnWillStopContext): Promise<string | undefined> {
ctx.signal.throwIfAborted();
if (this.stopHookContinuationUsed) return undefined;
const block = await this.engine()?.triggerBlock('Stop', {
signal: ctx.signal,
inputData: { stopHookActive: ctx.stopHookActive },
inputData: { stopHookActive: false },
});
ctx.signal.throwIfAborted();
return block?.reason;

View file

@ -6,6 +6,7 @@ import type { TurnResult } from './types';
export interface TurnBeforeStepContext {
readonly turnId: number;
readonly step: number;
readonly signal: AbortSignal;
}
@ -23,7 +24,6 @@ export interface TurnContextOverflowContext {
export interface TurnWillStopContext {
readonly signal: AbortSignal;
readonly stopHookActive: boolean;
continuationPrompt?: string;
}

View file

@ -71,7 +71,6 @@ export class AgentLoopService implements IAgentLoopService {
let stopReason: LoopTurnStopReason = 'completed';
let activeStep: number | undefined;
const maxSteps = this.config.get<LoopControl>(LOOP_CONTROL_SECTION)?.maxStepsPerTurn;
let stopHookContinuationUsed = false;
try {
while (true) {
@ -96,19 +95,16 @@ export class AgentLoopService implements IAgentLoopService {
continue;
}
if (!stopHookContinuationUsed) {
const context: TurnWillStopContext = { signal, stopHookActive: false };
await this.hooks.onWillStop.run(context);
if (context.continuationPrompt !== undefined && hasStepBudgetRemaining(maxSteps, steps)) {
stopHookContinuationUsed = true;
this.append({
role: 'user',
content: [{ type: 'text', text: context.continuationPrompt }],
toolCalls: [],
origin: { kind: 'system_trigger', name: 'stop_hook' },
});
continue;
}
const context: TurnWillStopContext = { signal };
await this.hooks.onWillStop.run(context);
if (context.continuationPrompt !== undefined) {
this.append({
role: 'user',
content: [{ type: 'text', text: context.continuationPrompt }],
toolCalls: [],
origin: { kind: 'system_trigger', name: 'stop_hook' },
});
continue;
}
break;
@ -150,7 +146,7 @@ export class AgentLoopService implements IAgentLoopService {
readonly stopReason: FinishReason;
readonly continueTurn: boolean;
}> {
await this.hooks.beforeStep.run({ turnId, signal });
await this.hooks.beforeStep.run({ turnId, step: currentStep, signal });
signal.throwIfAborted();
const stepUuid = randomUUID();
@ -228,7 +224,7 @@ export class AgentLoopService implements IAgentLoopService {
this.emitStepCompleted(turnId, currentStep, stepUuid, usage, finishReason, response);
const afterStepContext = { turnId, signal, usage, continueTurn: false };
const afterStepContext = { turnId, step: currentStep, signal, usage, continueTurn: false };
try {
await this.hooks.afterStep.run(afterStepContext);
} catch {
@ -383,10 +379,6 @@ function toolResultOutputForModel(result: ToolResult): string | ContentPart[] {
return output;
}
function hasStepBudgetRemaining(maxSteps: number | undefined, currentStep: number): boolean {
return maxSteps === undefined || maxSteps <= 0 || currentStep < maxSteps;
}
registerScopedService(
LifecycleScope.Agent,
IAgentLoopService,