Skip to content

feat(workflow-executor): add TriggerActionStepExecutor with confirmation flow#1501

Open
Scra3 wants to merge 23 commits intofeat/prd-219-update-record-step-executorfrom
feat/trigger-action-step-executor
Open

feat(workflow-executor): add TriggerActionStepExecutor with confirmation flow#1501
Scra3 wants to merge 23 commits intofeat/prd-219-update-record-step-executorfrom
feat/trigger-action-step-executor

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Mar 20, 2026

Summary

  • Implements TriggerActionStepExecutor following the UpdateRecordStepExecutor pattern (branches A/B/C, automaticExecution, confirmation flow)
  • AI selects an action via a select-action tool (Zod enum of display names + technical names as hints); resolveActionName maps displayName → technical name with name fallback
  • Adds TriggerActionStepExecutionData with executionParams: { actionDisplayName, actionName }, executionResult: { success } | { skipped }, and pendingAction for the awaiting-confirmation state
  • Adds NoActionsError for collections with no actions
  • Fixes buildStepSummary in BaseStepExecutor to include trigger-action pendingAction in prior-step AI context (parity with update-record pendingUpdate)
  • Action result intentionally discarded — privacy constraint (no client data leaves the infrastructure)

Test plan

  • Branch B (automaticExecution: true) — triggers action, saves executionParams with both display and technical name, returns success
  • Branch C (no automaticExecution) — saves pendingAction, returns awaiting-input
  • Branch A confirmed — triggers action, preserves pendingAction for traceability
  • Branch A rejected — action not called, saves { skipped: true }
  • Branch A no pending action — throws
  • NoActionsError — returns error outcome
  • WorkflowExecutorError from executeActionerror outcome (branches A & B)
  • Infrastructure errors propagate (branches A & B)
  • resolveActionName failure (Branch A — action deleted between confirmation steps)
  • displayName → technical name resolution + fallback
  • Multi-record AI selection
  • RunStore error propagation (5 scenarios across all branches)
  • AI malformed / missing tool call → error outcome
  • Default prompt fallback
  • Previous steps context in AI messages
  • Schema caching (single getCollectionSchema call per collection)
  • stepOutcome shape

🤖 Generated with Claude Code

Note

Add TriggerRecordActionStepExecutor with confirmation flow to workflow-executor

  • Adds TriggerRecordActionStepExecutor for a new trigger-action step type that selects a record, picks an action via AI, and either executes immediately or persists pendingData for user confirmation before executing.
  • Adds LoadRelatedRecordStepExecutor and McpTaskStepExecutor with the same automatic/confirmation branching pattern.
  • Introduces RecordTaskStepExecutor as a shared abstract base that standardizes confirmation flow, skipped-state persistence, and buildOutcomeResult for record-task steps.
  • Centralizes error handling in BaseStepExecutor: the new concrete execute() wraps doExecute(), catches WorkflowExecutorError to log with cause and return an error outcome, and converts unexpected throws into a generic error outcome.
  • Adds SafeAgentPort to wrap all AgentPort calls and normalize non-domain errors into AgentPortError; all executors now use this wrapper.
  • Extends the error hierarchy in errors.ts with ~15 new typed errors (e.g. StepPersistenceError, StepStateError, AgentPortError, McpToolInvocationError) each carrying a separate userMessage for end-user display.
  • Behavioral Change: AgentPort methods (getRecord, updateRecord, getRelatedData, executeAction) now accept typed query objects instead of positional arguments; all callers and tests updated accordingly.

Macroscope summarized 369a398.

…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>
@Scra3 Scra3 force-pushed the feat/trigger-action-step-executor branch from ac706e8 to c884d37 Compare March 20, 2026 10:45
@qltysh
Copy link

qltysh bot commented Mar 20, 2026

Qlty

Coverage Impact

⬆️ Merging this pull request will increase total coverage on feat/prd-219-update-record-step-executor by 0.05%.

Modified Files with Diff Coverage (17)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/http/executor-http-server.ts100.0%
Coverage rating: A Coverage rating: A
...workflow-executor/src/executors/update-record-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
...ages/workflow-executor/src/adapters/agent-client-agent-port.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/errors.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/ai-proxy/src/index.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/safe-agent-port.ts100.0%
New file Coverage rating: F
packages/workflow-executor/src/adapters/console-logger.ts50.0%5
New file Coverage rating: A
...ages/workflow-executor/src/executors/mcp-task-step-executor.ts100.0%
New file Coverage rating: A
...ow-executor/src/executors/load-related-record-step-executor.ts99.0%138
New file Coverage rating: A
...orkflow-executor/src/executors/summary/step-summary-builder.ts100.0%
New file Coverage rating: A
...s/workflow-executor/src/executors/record-task-step-executor.ts100.0%
New file Coverage rating: A
...-executor/src/executors/trigger-record-action-step-executor.ts100.0%
New file Coverage rating: A
...ow-executor/src/executors/summary/step-execution-formatters.ts100.0%
Total99.5%
🤖 Increase coverage with AI coding...

In the `feat/trigger-action-step-executor` branch, add test coverage for this new code:

- `packages/workflow-executor/src/adapters/console-logger.ts` -- Line 5
- `packages/workflow-executor/src/executors/load-related-record-step-executor.ts` -- Line 138

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

…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>
@qltysh
Copy link

qltysh bot commented Mar 20, 2026

6 new issues

Tool Category Rule Count
qlty Structure Function with high complexity (count = 18): execute 2
qlty Structure Deeply nested control flow (level = 4) 2
qlty Duplication Found 16 lines of similar code in 2 locations (mass = 92) 2

alban bertolini and others added 9 commits March 20, 2026 12:40
…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>
…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>
alban bertolini and others added 12 commits March 21, 2026 13:36
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant