refactor(workflow-executor): workflow steps#1502
Open
Scra3 wants to merge 45 commits intofeat/prd-214-setup-workflow-executor-packagefrom
Open
refactor(workflow-executor): workflow steps#1502Scra3 wants to merge 45 commits intofeat/prd-214-setup-workflow-executor-packagefrom
Scra3 wants to merge 45 commits intofeat/prd-214-setup-workflow-executor-packagefrom
Conversation
…on flow Implements PRD-219. Adds an executor for update-record steps with three execution branches: automatic completion, awaiting user confirmation, and re-entry after confirmation. Extracts shared record selection helpers from ReadRecordStepExecutor into BaseStepExecutor for reuse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cordStepExecutor Match on type + stepIndex only, then guard on toolConfirmationInterruption presence separately. Removes redundant filter predicate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…endingUpdate The field stores the proposed field update (from AI or user-edited) pending confirmation. "pendingUpdate" better describes the content than "toolConfirmationInterruption" which described the mechanism. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…f error outcome Missing pendingUpdate in Branch A is a bug (orchestrator/RunStore), not a business case — throw WorkflowExecutorError instead of returning a graceful error outcome. Also rename local variable interruption → execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… casting
Add { skipped: true } to the executionResult union type, removing the
unsafe `as unknown as` cast when the user rejects the update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… traceability Keep pendingUpdate in the saved execution data after both confirm and reject — useful for audit trail (what was proposed vs what happened). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ecutor
Pass userInput as parameter to handleConfirmation() where it is already
narrowed, removing the `as { confirmed: boolean }` cast. Also move
resolveFieldName inside try-catch blocks so WorkflowExecutorError is
properly caught and returned as a status: 'error' outcome.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…prove coverage - Extract resolveAndUpdate to deduplicate resolve+update+persist logic - Extract buildOutcomeResult/buildSuccessResult/buildErrorResult helpers - Move getCollectionSchema inside try-catch in confirmation flow - Rename executionParams.fieldName to fieldDisplayName for consistency - Add tests for resolveFieldName failure in both branches - Add test for relationship field exclusion from update tool schema Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dErrorResult wrappers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ment executionResult Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…StepExecutionData Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…field resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move findField to BaseStepExecutor with displayName priority over fieldName - Move buildOutcomeResult to BaseStepExecutor, use error !== undefined check - Fix resolveAndUpdate try-catch scope (saveStepExecution outside try block) - Fix ConditionStepExecutor catch-all to only catch WorkflowExecutorError - Add pendingUpdate visibility in step summary - Remove findField from types/record.ts and barrel export Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Condition steps handle BPMN routing, record-task steps operate on client data. The previous "ai-task" name was misleading since both step types use AI. Rename to "record-task" to reflect the actual distinction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Definition Drop recordSourceStepId and remoteToolsSourceId — both were declared but never read. allowedTools is kept as the orchestrator-provided tool filter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces StepType.ToolTask ('tool-task') and ToolTaskStepDefinition for
steps where the AI freely selects and executes a tool from allowedTools.
Moves allowedTools out of RecordTaskStepDefinition where it didn't belong.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…AndUpdate arity
- Extract UpdateTarget interface {selectedRecordRef, fieldDisplayName, value} so
resolveAndUpdate takes 2 params instead of 4 (addresses qlty reviewer comment)
- Collapse 3 uninitialized let declarations in handleFirstCall into one target object
- Fix missing runId in getAvailableRecordRefs and update-record saveStepExecution calls
- Strengthen test assertions to include runId as first arg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecutor Code changes: - Add JSDoc on buildOutcomeResult warning it is for record-task executors only - Add runtime guard on userInput.type in handleConfirmation (WorkflowExecutorError if an unexpected type is received, guarding against future UserInput union extension) New tests: - buildStepSummary: pendingUpdate branch coverage (update-record step with pendingUpdate but no executionParams emits Pending: in AI context) - handleConfirmation: stepIndex mismatch and missing pendingUpdate cases - stepOutcome shape: type/stepId/stepIndex asserted on happy path - unexpected userInput type: runtime guard verified - findField fieldName fallback: update succeeds when AI returns raw fieldName - schema caching: getCollectionSchema called once per collection in Branch B - RunStore error propagation: all four saveStepExecution/getStepExecutions call sites Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pletion→automaticExecution - StepType.ToolTask → StepType.McpTask (wire value: 'tool-task' → 'mcp-task') - ToolTaskStepDefinition → McpTaskStepDefinition - mcpServerId: string[] → string (single ID) - automaticCompletion → automaticExecution on both RecordTaskStepDefinition and McpTaskStepDefinition - Remove TODO rename comments - Update all tests accordingly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…record-1' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the UserInput discriminated union with a plain boolean field userConfirmed on ExecutionContext and PendingStepExecution. Since the only user input type in practice is a boolean confirmation, the union wrapper adds complexity with no benefit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion flow
Implements TriggerActionStepExecutor following the UpdateRecordStepExecutor
pattern (branches A/B/C, confirmation flow, automaticExecution).
- Add TriggerActionStepExecutionData type with executionParams (actionDisplayName
+ actionName), executionResult ({ success } | { skipped }), and pendingAction
- Add NoActionsError for collections with no actions
- Implement selectAction via AI tool with displayName enum and technical name hints
- resolveAndExecute stores the technical actionName in executionParams for
traceability; action result discarded per privacy constraint
- Fix buildStepSummary in BaseStepExecutor to include trigger-action pendingAction
in prior-step AI context (parity with update-record pendingUpdate)
- Export TriggerActionStepExecutor, TriggerActionStepExecutionData, NoActionsError
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t and pendingAction Resolve actionName once in handleFirstCall and store it in pendingAction, so resolveAndExecute receives it directly via ActionTarget without re-fetching the schema. Rename TriggerTarget → ActionTarget for consistency with English naming conventions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…f shapes to { name, displayName }
- Extract ActionRef { name, displayName } from inline types in TriggerActionStepExecutionData
- Extract FieldRef { name, displayName } replacing FieldReadBase in ReadRecord types
- UpdateRecordStepExecutionData executionParams/pendingUpdate now use FieldRef & { value }
- Rename actionDisplayName/actionName → displayName/name, fieldDisplayName/fieldName → displayName/name
- Move resolveFieldName to handleFirstCall (no re-resolution in resolveAndUpdate)
- Add missing tests: resolveActionName not-found path, saveStepExecution not-called assertions, trigger-action pendingAction in buildStepSummary
- Export ActionRef, FieldRef, NoActionsError from index; update CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…store display names in executionParams Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rams for consistency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-executor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y" wording Use fieldNames instead of fieldDisplayNames and actionName instead of displayName in tool schemas exposed to the AI, to avoid confusion between property names and their semantics (values remain display names). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ize pending state - Make buildOutcomeResult abstract on BaseStepExecutor; each branch owns its outcome shape - Add RecordTaskStepExecutor intermediate class implementing buildOutcomeResult for 'record-task' - Add pendingData to BaseStepExecutionData, replacing pendingUpdate/pendingAction per-type fields; each executor sets it when saving, base class reads it directly with no type discrimination - Rename TriggerActionStepExecutor → TriggerRecordActionStepExecutor for naming consistency with ReadRecord/UpdateRecord Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…idate error handling - Introduce doExecute() as the abstract hook; execute() in the base class wraps it with the single try-catch that converts WorkflowExecutorErrors to error outcomes and rethrows infrastructure errors — removing all try-catch boilerplate from subclasses - Inline the former buildErrorOutcomeOrThrow helper directly into execute(), it no longer needs to be a named method - Add StepPersistenceError (extends WorkflowExecutorError) for post-side-effect persistence failures; these are now consistently converted to error outcomes - Extract handleConfirmationFlow into RecordTaskStepExecutor to DRY the confirmation pattern shared by update-record and trigger-record-action - Split the !execution?.pendingData guard into two distinct WorkflowExecutorErrors for clearer debug messages - Export BaseStepStatus from step-outcome; remove pendingData from BaseStepExecutionData (declared only on the concrete types that need it) - Fix extractToolCallArgs to throw MalformedToolCallError when args is null/undefined Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…error hierarchy, and AI-first Branch C - Add LoadRelatedRecordStepExecutor with Branch A/B/C confirmation flow - Replace candidates[] in LoadRelatedRecordPendingData with AI-selected suggestedRecordId/suggestedFields/relatedCollectionName - Extract selectBestFromRelatedData to share AI selection logic between Branch B and Branch C - Make WorkflowExecutorError abstract; introduce InvalidAIResponseError, RelationNotFoundError, FieldNotFoundError, ActionNotFoundError, StepStateError - Fix selectRelevantFields to use displayName in Zod enum and map back to fieldName - Add getRelatedData fields param forwarding in AgentClientAgentPort - Update CLAUDE.md with displayName-in-AI-tools and error hierarchy rules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 new issues
|
Comment on lines
+1467
to
+1469
| expect(selectRecordTool.schema.shape.recordIdentifier.options).not.toContain( | ||
| expect.stringContaining('stepIndex: 3'), | ||
| ); |
There was a problem hiding this comment.
🟢 Low executors/load-related-record-step-executor.test.ts:1467
The assertion not.toContain(expect.stringContaining('stepIndex: 3')) at line 1467 always passes because stringContaining('stepIndex: 3') never matches the 'Step N - Collection #id' format used by select-record tool options (e.g., 'Step 2 - Orders #99'). This gives false confidence that pending executions without a record are excluded from the pool. Consider asserting against the actual identifier format, such as checking that the options array has length 2 and excludes 'Step 3 - Invoices #55'.
- expect(selectRecordTool.schema.shape.recordIdentifier.options).not.toContain(
- expect.stringContaining('stepIndex: 3'),
- );
+ expect(selectRecordTool.schema.shape.recordIdentifier.options).toHaveLength(2);
+ expect(selectRecordTool.schema.shape.recordIdentifier.options).not.toContain(
+ 'Step 3 - Invoices #55',
+ );🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts around lines 1467-1469:
The assertion `not.toContain(expect.stringContaining('stepIndex: 3'))` at line 1467 always passes because `stringContaining('stepIndex: 3')` never matches the `'Step N - Collection #id'` format used by `select-record` tool options (e.g., `'Step 2 - Orders #99'`). This gives false confidence that pending executions without a `record` are excluded from the pool. Consider asserting against the actual identifier format, such as checking that the options array has length 2 and excludes `'Step 3 - Invoices #55'`.
Evidence trail:
1. Test assertion at packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts:1467-1469 - shows `not.toContain(expect.stringContaining('stepIndex: 3'))`
2. Record identifier format at packages/workflow-executor/src/executors/base-step-executor.ts:228-233 - comment says 'Step X - CollectionDisplayName #id' and implementation shows: `Step ${record.stepIndex} - ${schema.collectionDisplayName} #${record.recordId}`
3. Test mock at line 1427: `{ recordIdentifier: 'Step 2 - Orders #99' }` confirms the actual format used
4. pendingExecution definition at lines 1388-1398 shows stepIndex: 3 with pendingData.displayName: 'Invoice'
…ser facing messages Separates technical error messages (dev logs) from user-facing messages (Forest Admin UI). WorkflowExecutorError now carries a readonly `userMessage` field; base-step-executor uses it instead of `message` when building the step outcome error. Each subclass declares its own user-oriented message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nject logger into execution context Introduces a Logger interface (port) and ConsoleLogger (default implementation). Adds logger to ExecutionContext, masks raw error messages from the HTTP API (security), and logs unhandled HTTP errors with context instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eLogger output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ated-record executor, rename ids→id
- Add StepExecutionFormatters (static per-type formatters) and StepSummaryBuilder
(orchestrates step summary for AI context); decouple formatting from BaseStepExecutor
- Load-related-record executionResult is now self-contained: { relation: RelationRef; record: RecordRef }
- Rename ids → id in AgentPort and all callers (id is a composite key of one record, not multiple records)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve summary to sub-folder - Replace QueryBase intersections with named query types (GetRecordQuery, UpdateRecordQuery, GetRelatedDataQuery, ExecuteActionQuery) for better DX and readability - Save executeAction return value in executionResult.actionResult instead of discarding it - Move StepSummaryBuilder and StepExecutionFormatters to executors/summary/ sub-folder Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…edData signature Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… into McpTaskStepExecutor remoteTools was a dependency of McpTaskStepExecutor only — 5 of 6 executors never used it. Inject it explicitly via the constructor so ExecutionContext stays focused on execution state, and remove the now-meaningless remoteTools: [] boilerplate from every other executor test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…utor Any WorkflowExecutorError with a cause is now logged automatically by the base executor using error.message as the log message. Removes the manual logger.error call from McpTaskStepExecutor and the StepPersistenceError- specific guard. Moves cause? up to the base class, removing duplicate declarations from StepPersistenceError and McpToolInvocationError. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AgentPortError and SafeAgentPort to centralise infra error handling for all agentPort operations (getRecord, updateRecord, getRelatedData, executeAction). - add AgentPortError extending WorkflowExecutorError with a user-friendly message and structured cause logging - add SafeAgentPort implementing AgentPort: wraps infra errors in AgentPortError, passes through WorkflowExecutorError subclasses unchanged - expose this.agentPort (SafeAgentPort) as a protected property in BaseStepExecutor; all executors use it instead of this.context.agentPort - export AgentPortError from index.ts - add unit tests for SafeAgentPort and one integration test per executor verifying userMessage and logger.error cause on infra failures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ask dependencies Add McpToolRef, McpToolCall and McpTaskStepExecutionData to step-execution-data, required by mcp-task-step-executor. Update package.json to depend on @forestadmin/ai-proxy and align @langchain/core version. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Export RemoteTool default, BaseChatModel, BaseMessage, HumanMessage, SystemMessage, StructuredToolInterface and DynamicStructuredTool so consumers of ai-proxy do not need a direct @langchain/core dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…restadmin/ai-proxy Replace all direct @langchain/core imports in src and tests with @forestadmin/ai-proxy re-exports. Add moduleNameMapper in jest.config.ts to resolve @anthropic-ai/sdk wildcard exports (Jest < 30 workaround, same pattern as ai-proxy). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@langchain/core is now consumed transitively via @forestadmin/ai-proxy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
ActionRefandFieldRefshared interfaces ({ name, displayName }) from inline types inTriggerActionStepExecutionDataandUpdateRecordStepExecutionDataFieldReadBase(private) with exportedFieldRef—FieldReadSuccess/FieldReadErrornow extendFieldRefactionDisplayName/actionName→displayName/nameandfieldDisplayName/fieldName→displayName/namefor consistencyresolveFieldNametohandleFirstCallso the technical field name is stored inpendingUpdateat creation time (no re-resolution inresolveAndUpdate)resolveActionNamenot-found path,saveStepExecutionnot-called assertions onWorkflowExecutorError,trigger-actionpending action inbuildStepSummaryActionRef,FieldRef,NoActionsErrorfrom index; updateCLAUDE.mdTest plan
yarn workspace @forestadmin/workflow-executor test— 144 tests passyarn workspace @forestadmin/workflow-executor build— no errorsyarn workspace @forestadmin/workflow-executor lint— clean🤖 Generated with Claude Code
Note
Add new workflow step executors for updating records, triggering actions, loading related records, and MCP tasks
UpdateRecordStepExecutor,TriggerRecordActionStepExecutor,LoadRelatedRecordStepExecutor, andMcpTaskStepExecutor, each supporting automatic execution, user confirmation flow, and pending-data persistence.BaseStepExecutorto centralize error handling inexecute()(catching and converting errors to user-facing outcomes), add AI-assisted record/field/action selection helpers, schema caching, and aSafeAgentPortwrapper that normalizes unexpected errors intoAgentPortError.AiTask*types toRecordTask*and adds a newmcp-taskStepType; updatesAgentPortmethod signatures to accept structured query objects instead of positional parameters.Loggerport andConsoleLoggeradapter; integrates optional logging intoBaseStepExecutorandExecutorHttpServer, which now returns a generic'Internal server error'body instead of exposing error messages.@langchain/corewith@forestadmin/ai-proxyas the direct dependency for LangChain types across the package.AgentPortinterface is a breaking change — all implementations must be updated to use the new object-based query signatures.Macroscope summarized 369a398.