diff --git a/packages/workflow-executor/src/adapters/forest-server-workflow-port.ts b/packages/workflow-executor/src/adapters/forest-server-workflow-port.ts index 7cba97f694..5fc111d2e2 100644 --- a/packages/workflow-executor/src/adapters/forest-server-workflow-port.ts +++ b/packages/workflow-executor/src/adapters/forest-server-workflow-port.ts @@ -131,7 +131,7 @@ export default class ForestServerWorkflowPort implements WorkflowPort { run: ServerHydratedWorkflowRun, err: WorkflowExecutorError, ): MalformedRunInfo { - const pending = run.workflowHistory.find(s => !s.done && !s.cancelled && !s.context?.error); + const pending = run.workflowHistory.at(-1) ?? null; return { runId: String(run.id), diff --git a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts index f86a074c45..02953a75f1 100644 --- a/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts +++ b/packages/workflow-executor/src/adapters/run-to-available-step-mapper.ts @@ -136,8 +136,8 @@ export default function toAvailableStepExecution( ); } - const pending = run.workflowHistory.find(s => !s.done && !s.cancelled && !s.context?.error); - if (!pending) return null; + const pending = run.workflowHistory.at(-1) ?? null; + if (!pending || pending.done) return null; const result = { runId: String(run.id), diff --git a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts index 99feb048ef..f84edcf5a7 100644 --- a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts @@ -94,41 +94,18 @@ describe('toAvailableStepExecution', () => { expect(result?.baseRecordRef.recordId).toEqual(['rec-abc']); }); - it('should return null when all steps are done', () => { - const run = makeRun({ - workflowHistory: [ - makeStepHistory({ stepIndex: 0, done: true }), - makeStepHistory({ stepIndex: 1, done: true }), - ], - }); - - expect(toAvailableStepExecution(run)).toBeNull(); - }); - - it('should return null when all steps are done or cancelled', () => { - const run = makeRun({ - workflowHistory: [ - makeStepHistory({ stepIndex: 0, done: true }), - makeStepHistory({ stepIndex: 1, done: false, cancelled: true }), - ], - }); - - expect(toAvailableStepExecution(run)).toBeNull(); - }); - it('should return null when workflowHistory is empty', () => { const run = makeRun({ workflowHistory: [] }); expect(toAvailableStepExecution(run)).toBeNull(); }); - it('should pick the first non-done, non-cancelled step as pending', () => { + it('picks the last step — orchestrator is the source of truth for which step to execute', () => { const run = makeRun({ workflowHistory: [ makeStepHistory({ stepName: 's0', stepIndex: 0, done: true }), - makeStepHistory({ stepName: 's1', stepIndex: 1, done: false, cancelled: true }), + makeStepHistory({ stepName: 's1', stepIndex: 1, done: true }), makeStepHistory({ stepName: 's2', stepIndex: 2, done: false }), - makeStepHistory({ stepName: 's3', stepIndex: 3, done: false }), ], }); @@ -138,33 +115,6 @@ describe('toAvailableStepExecution', () => { expect(result?.stepIndex).toBe(2); }); - it('errored step (done:false + context.error) is skipped — next pending step is returned', () => { - // Scenario: back changed errored steps to done:false so the front can offer Continue/Revise. - // The executor must skip the errored step and pick the next pending one. - const run = makeRun({ - workflowHistory: [ - makeStepHistory({ stepName: 's0', stepIndex: 0, done: false, context: { error: 'boom' } }), - makeStepHistory({ stepName: 's1', stepIndex: 1, done: false }), - ], - }); - - const result = toAvailableStepExecution(run); - - expect(result?.stepId).toBe('s1'); - expect(result?.stepIndex).toBe(1); - }); - - it('returns null when the only non-done step is errored', () => { - const run = makeRun({ - workflowHistory: [ - makeStepHistory({ stepIndex: 0, done: true }), - makeStepHistory({ stepIndex: 1, done: false, context: { error: 'failed' } }), - ], - }); - - expect(toAvailableStepExecution(run)).toBeNull(); - }); - it('should strip unknown server keys (e.g. automaticExecution) from guidance step without throwing', () => { const run = makeRun({ workflowHistory: [ @@ -404,18 +354,19 @@ describe('toAvailableStepExecution', () => { }); }); - it('should not include done steps that are after the available step', () => { + it('should not include the pending step itself in previousSteps', () => { const run = makeRun({ workflowHistory: [ - makeStepHistory({ stepName: 's0', stepIndex: 0, done: false }), - makeStepHistory({ stepName: 's1', stepIndex: 1, done: true }), + makeStepHistory({ stepName: 's0', stepIndex: 0, done: true }), + makeStepHistory({ stepName: 's1', stepIndex: 1, done: false }), ], }); const result = toAvailableStepExecution(run); - expect(result?.stepId).toBe('s0'); - expect(result?.previousSteps).toHaveLength(0); + expect(result?.stepId).toBe('s1'); + expect(result?.previousSteps).toHaveLength(1); + expect(result?.previousSteps[0].stepOutcome.stepId).toBe('s0'); }); it.each([