feat(workflow-executor): scaffold @forestadmin/workflow-executor package#1493
feat(workflow-executor): scaffold @forestadmin/workflow-executor package#1493
Conversation
…premature deps, add smoke test - Rewrite CLAUDE.md with project overview and architecture principles, remove changelog - Remove unused dependencies (ai-proxy, sequelize, zod) per YAGNI - Add smoke test so CI passes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
083894b to
4510b7b
Compare
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (12) 🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
… document system architecture
- Lint now covers src and test directories
- Replace require() with import, use stronger assertion (toHaveLength)
- Add System Architecture section describing Front/Orchestrator/Executor/Agent
- Mark Architecture Principles as planned (not yet implemented)
- Remove redundant test/.gitkeep
- Make index.ts a valid module with export {}
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| export type McpConfiguration = unknown; | ||
|
|
||
| export interface WorkflowPort { | ||
| getPendingStepExecutions(): Promise<PendingStepExecution[]>; |
There was a problem hiding this comment.
this method will retrieve the workflowRun and workflowSteps of one pending run. It will take an optional runId, and return an object with complete workflowRun, workflowSteps, workflowRecords
| interface BaseStepHistory { | ||
| stepId: string; | ||
| stepIndex: number; | ||
| status: StepStatus; | ||
| /** Present when status is 'error'. */ | ||
| error?: string; | ||
| } | ||
|
|
||
| export interface ConditionStepHistory extends BaseStepHistory { | ||
| type: 'condition'; | ||
| /** Present when status is 'success'. */ | ||
| selectedOption?: string; | ||
| } | ||
|
|
||
| export interface AiTaskStepHistory extends BaseStepHistory { | ||
| type: 'ai-task'; | ||
| } |
There was a problem hiding this comment.
what are those types ? this does not correspond to anything
| }; | ||
| } | ||
|
|
||
| // agent-client methods (update, relation, action) still expect the pipe-encoded string format |
There was a problem hiding this comment.
🟢 Low adapters/agent-client-agent-port.ts:25
encodePk joins primary key values with | without escaping, so a value containing a pipe (e.g., ['user|123', 'tenant1']) encodes to the same string as a different composite key (['user', '123', 'tenant1']). This causes update, relation, and action operations to target the wrong record when primary key values contain pipe characters.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around line 25:
`encodePk` joins primary key values with `|` without escaping, so a value containing a pipe (e.g., `['user|123', 'tenant1']`) encodes to the same string as a different composite key (`['user', '123', 'tenant1']`). This causes `update`, `relation`, and `action` operations to target the wrong record when primary key values contain pipe characters.
Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts:25-27 (REVIEWED_COMMIT) - encodePk function joins with '|' without escaping; packages/agent/src/utils/id.ts:26 - packId also joins with '|'; packages/agent/src/utils/id.ts:43 - unpackId splits with '|' without unescaping
| return { ...ref, recordId, values: updatedRecord }; | ||
| } | ||
|
|
||
| async getRelatedData( |
There was a problem hiding this comment.
🟡 Medium adapters/agent-client-agent-port.ts:76
getRelatedData passes relationName (the field name, e.g., 'author') to getCollectionRef, which looks up collection metadata by collection name. This returns a fallback CollectionRef with primaryKeyFields: ['id'] instead of the actual related collection's metadata. Consequently, extractRecordId extracts the wrong fields from related records, returning incorrect or undefined values for the primary key. Consider obtaining the target collection name from the relation's metadata rather than using the field name directly.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around line 76:
`getRelatedData` passes `relationName` (the field name, e.g., `'author'`) to `getCollectionRef`, which looks up collection metadata by collection name. This returns a fallback `CollectionRef` with `primaryKeyFields: ['id']` instead of the actual related collection's metadata. Consequently, `extractRecordId` extracts the wrong fields from related records, returning incorrect or `undefined` values for the primary key. Consider obtaining the target collection name from the relation's metadata rather than using the field name directly.
Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts lines 72-87 (getRelatedData method passes relationName to getCollectionRef at line 76), lines 100-112 (getCollectionRef looks up by collectionName and returns fallback with primaryKeyFields: ['id']), packages/workflow-executor/test/adapters/agent-client-agent-port.test.ts lines 152-174 (test only works because relation name 'posts' matches collection name 'posts'; fallback test confirms behavior when they don't match)
2 new issues
|
| function extractRecordId( | ||
| primaryKeyFields: string[], | ||
| record: Record<string, unknown>, | ||
| ): Array<string | number> { | ||
| return primaryKeyFields.map(field => record[field] as string | number); | ||
| } |
There was a problem hiding this comment.
🟢 Low adapters/agent-client-agent-port.ts:30
extractRecordId returns undefined for missing primary key fields, but the string | number type assertion hides this. When passed to encodePk, undefined becomes the literal string "undefined", causing lookups to silently fail with wrong record IDs. Consider adding a runtime check for missing fields and throwing an error, or return the actual values and let encodePk validate them.
function extractRecordId(
primaryKeyFields: string[],
record: Record<string, unknown>,
): Array<string | number> {
- return primaryKeyFields.map(field => record[field] as string | number);
+ return primaryKeyFields.map(field => {
+ const value = record[field];
+ if (value === undefined || value === null) {
+ throw new Error(`Missing primary key field: ${field}`);
+ }
+ return value as string | number;
+ });
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around lines 30-35:
`extractRecordId` returns `undefined` for missing primary key fields, but the `string | number` type assertion hides this. When passed to `encodePk`, `undefined` becomes the literal string `"undefined"`, causing lookups to silently fail with wrong record IDs. Consider adding a runtime check for missing fields and throwing an error, or return the actual values and let `encodePk` validate them.
Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts lines 25-33 (REVIEWED_COMMIT): `encodePk` uses `String(v)` which converts undefined to "undefined"; `extractRecordId` uses type assertion `as string | number` on `record[field]` which can be undefined at runtime. Line 83 shows `extractRecordId` is called in `getRelatedData` to create record IDs that are returned and could be used in subsequent operations.
| // TODO: finalize route paths with the team — these are placeholders | ||
| const ROUTES = { | ||
| pendingStepExecutions: '/liana/v1/workflow-step-executions/pending', | ||
| updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`, | ||
| collectionRef: (collectionName: string) => `/liana/v1/collections/${collectionName}`, | ||
| mcpServerConfigs: '/liana/mcp-server-configs-with-details', | ||
| }; |
There was a problem hiding this comment.
🟢 Low adapters/forest-server-workflow-port.ts:9
ROUTES.updateStepExecution(runId) and ROUTES.collectionRef(collectionName) interpolate raw values into URL paths without encoding, so special characters like /, ?, or % in runId or collectionName produce malformed URLs (e.g., collectionName="a/b" becomes /liana/v1/collections/a/b with three path segments). Consider wrapping the parameters with encodeURIComponent() to ensure they are safely encoded.
-// TODO: finalize route paths with the team — these are placeholders
const ROUTES = {
pendingStepExecutions: '/liana/v1/workflow-step-executions/pending',
- updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`,
- collectionRef: (collectionName: string) => `/liana/v1/collections/${collectionName}`,
+ updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${encodeURIComponent(runId)}/complete`,
+ collectionRef: (collectionName: string) => `/liana/v1/collections/${encodeURIComponent(collectionName)}`,
mcpServerConfigs: '/liana/mcp-server-configs-with-details',
};🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/forest-server-workflow-port.ts around lines 9-15:
`ROUTES.updateStepExecution(runId)` and `ROUTES.collectionRef(collectionName)` interpolate raw values into URL paths without encoding, so special characters like `/`, `?`, or `%` in `runId` or `collectionName` produce malformed URLs (e.g., `collectionName="a/b"` becomes `/liana/v1/collections/a/b` with three path segments). Consider wrapping the parameters with `encodeURIComponent()` to ensure they are safely encoded.
Evidence trail:
packages/workflow-executor/src/adapters/forest-server-workflow-port.ts lines 11-13 (ROUTES definitions with template literals), packages/forestadmin-client/src/utils/server.ts lines 63-70 (queryWithHeaders passes path directly to new URL() without encoding)

Summary
@forestadmin/workflow-executorpackage (tsconfig, jest, eslint, CI matrix)fixes PRD-214
Test plan
yarn workspace @forestadmin/workflow-executor buildpassesyarn workspace @forestadmin/workflow-executor lintpassesyarn workspace @forestadmin/workflow-executor testpasses🤖 Generated with Claude Code
Note
Scaffold
@forestadmin/workflow-executorpackage in the monorepoAdds the initial package structure for
@forestadmin/workflow-executorwith an emptysrc/index.ts, TypeScript and ESLint configs, Jest setup, and apackage.jsonconfigured for the workspace. The package is also added to the CI matrix in build.yml so it participates in build and test jobs.Changes since #1493 opened
BaseStepExecutorabstract class andConditionStepExecutorconcrete implementation [127b579]RunStoreinterface to scope all methods to the current run by removingrunIdparameter [127b579]ConditionStepDefinition.optionstype constraint to require at least one option [127b579]@langchain/coreversion 1.1.33 andzodversion 4.3.6 as runtime dependencies to@forestadmin/workflow-executorpackage [127b579]@forestadmin/workflow-executorpackage public API [127b579]BaseStepExecutorandConditionStepExecutor[127b579]CLAUDE.md[127b579]RecordRefwithCollectionRefand support composite primary keys as arrays [cb8036b]AgentClientAgentPortadapter class with methods for record operations, related data access, and action execution [cb8036b]RecordNotFoundErrorclass, updated package exports, added@forestadmin/agent-clientdependency, and created test suite forAgentClientAgentPort[cb8036b]AiClientclass in@forestadmin/ai-proxypackage to manage AI model instances and MCP tool lifecycle [0ebae51]Routerclass in@forestadmin/ai-proxyto use extracted validation and configuration utilities [0ebae51]createBaseChatModelfunction andAiClientclass from@forestadmin/ai-proxypackage public API [0ebae51]AiClientclass,createBaseChatModelfunction, andgetAiConfigurationfunction [0ebae51]ForestServerWorkflowPortadapter class that implementsWorkflowPortinterface using@forestadmin/forestadmin-clientServerUtils.queryto communicate with Forest server endpoints [c25a953]WorkflowPortinterface method fromcompleteStepExecutiontoupdateStepExecution[c25a953]@forestadmin/forestadmin-clientversion1.37.17as dependency to@forestadmin/workflow-executorpackage and exportedForestServerWorkflowPortfrom package index [c25a953]ServerUtilsas named export from@forestadmin/forestadmin-clientpackage [c25a953]Macroscope summarized 17f26ca.