diff --git a/src/agent-runtime.ts b/src/agent-runtime.ts index 68652d6..5fdacdc 100644 --- a/src/agent-runtime.ts +++ b/src/agent-runtime.ts @@ -1090,6 +1090,15 @@ export class AgentRuntime { return stepEndData; } + /** + * User-friendly alias for emitStepEnd(). + * + * Keeps step lifecycle naming symmetric with beginStep(). + */ + endStep(opts: Parameters[0] = {}): any { + return this.emitStepEnd(opts); + } + private async captureArtifactFrame(): Promise { if (!this.artifactBuffer) { return; diff --git a/src/debugger.ts b/src/debugger.ts index 80380eb..7a58b18 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -7,9 +7,11 @@ import { Tracer } from './tracing/tracer'; export class SentienceDebugger { readonly runtime: AgentRuntime; private stepOpen: boolean = false; + private autoStep: boolean = true; - constructor(runtime: AgentRuntime) { + constructor(runtime: AgentRuntime, options?: { autoStep?: boolean }) { this.runtime = runtime; + this.autoStep = options?.autoStep !== undefined ? Boolean(options.autoStep) : true; } static attach(page: Page, tracer: Tracer, options?: AttachOptions): SentienceDebugger { @@ -43,6 +45,13 @@ export class SentienceDebugger { check(predicate: Predicate, label: string, required: boolean = false) { if (!this.stepOpen) { + if (!this.autoStep) { + throw new Error( + `No active step. Call dbg.beginStep(...) or dbg.step(...) before check(label=${JSON.stringify( + label + )}).` + ); + } this.beginStep(`verify:${label}`); } return this.runtime.check(predicate, label, required); diff --git a/tests/agent-runtime-attach.test.ts b/tests/agent-runtime-attach.test.ts index 0b50ffa..679d918 100644 --- a/tests/agent-runtime-attach.test.ts +++ b/tests/agent-runtime-attach.test.ts @@ -67,3 +67,15 @@ describe('AgentRuntime.fromPlaywrightPage()', () => { spy.mockRestore(); }); }); + +describe('AgentRuntime.endStep()', () => { + it('aliases emitStepEnd()', () => { + const runtime: any = { + emitStepEnd: jest.fn().mockReturnValue({ ok: true }), + }; + + const out = (AgentRuntime.prototype as any).endStep.call(runtime, { action: 'noop' }); + expect(runtime.emitStepEnd).toHaveBeenCalledWith({ action: 'noop' }); + expect(out).toEqual({ ok: true }); + }); +}); diff --git a/tests/debugger.test.ts b/tests/debugger.test.ts index e3a1267..3c2f189 100644 --- a/tests/debugger.test.ts +++ b/tests/debugger.test.ts @@ -66,4 +66,17 @@ describe('SentienceDebugger', () => { expect(runtime.check).toHaveBeenCalled(); expect(handle).toBe('handle'); }); + + it('can disable auto-step (strict mode)', () => { + const runtime = { + beginStep: jest.fn().mockReturnValue('step-1'), + check: jest.fn().mockReturnValue('handle'), + } as unknown as AgentRuntime; + + const dbg = new SentienceDebugger(runtime, { autoStep: false } as any); + + expect(() => + dbg.check((_ctx: any) => ({ passed: true, reason: '', details: {} }), 'has_cart') + ).toThrow(/No active step/i); + }); });