feat(workflow): add wait/resolve support for human-in-the-loop workflows#985
feat(workflow): add wait/resolve support for human-in-the-loop workflows#985toiroakr wants to merge 1 commit into
Conversation
Add typed `waitPoints` config to `createWorkflowJob` and `waitPoint<P, R>()` helper for defining wait/resolve points. The body context receives a typed `wait` function; callers use `job.resolve()` to resume waiting executions. Bundler injects `tailor.workflow.wait` into entry code and transforms `.resolve()` calls to `tailor.workflow.resolve()` with argument reordering. Local testing uses in-memory promise coordination.
|
⚡ pkg.pr.new@tailor-platform/sdk@tailor-platform/create-sdk
|
📖 Docs Consistency Check
|
| File | Issue | Suggested Fix |
|---|---|---|
packages/sdk/docs/services/workflow.md |
New wait/resolve feature not documented | Add section documenting waitPoints, waitPoint(), wait(), and job.resolve() |
CLAUDE.md |
Wait points not mentioned in workflow gotchas | Add non-obvious rules about wait/resolve behavior |
packages/sdk/docs/testing.md |
setupWaitPointMock() utility not documented |
Add section on testing workflows with wait points |
Details
1. packages/sdk/docs/services/workflow.md
Issue: The workflow documentation does not cover the new wait/resolve feature for human-in-the-loop workflows.
Missing API documentation:
waitPointsconfig option increateWorkflowJob(seepackages/sdk/src/configure/services/workflow/job.ts:228)waitPoint<Payload, Result>()helper function (seepackages/sdk/src/configure/services/workflow/wait-point.ts:90)wait(key, payload?)function available in job body context whenwaitPointsis definedjob.resolve(key, executionId, callback)method for resuming waiting executions
Example from the implementation:
export const processOrder = createWorkflowJob({
name: "process-order",
waitPoints: {
approval: waitPoint<{ message: string }, { approved: boolean }>(),
},
body: async (input: { orderId: string }, { wait }) => {
const result = await wait("approval", { message: `Approve ${input.orderId}?` });
return { orderId: input.orderId, approved: result.approved };
},
});
// From resolver/executor:
await processOrder.resolve("approval", executionId, (payload) => {
return { approved: true };
});Suggested location: Add a new section after "Triggering Jobs" (line 88) titled "Wait Points for Human-in-the-Loop Workflows"
2. CLAUDE.md
Issue: The "Non-obvious Rules and Gotchas" > "Workflows" section (lines 51-57) does not mention wait points.
Missing information:
- When a job declares
waitPoints, the context includes a typedwaitfunction - The bundler transforms
.resolve()calls to reorder arguments:job.resolve("key", execId, cb)→tailor.workflow.resolve(execId, "key", cb) - Local testing uses in-memory promise coordination; production uses platform wait/resolve
Suggested addition: Add to the workflow gotchas section:
- Jobs with `waitPoints` receive a typed `wait()` function in the body context
- `.resolve()` argument order is transformed during bundling (key comes before executionId in source, executionId comes first in runtime)3. packages/sdk/docs/testing.md
Issue: The "Workflow Tests" section (lines 174-253) does not cover testing workflows with wait points.
Missing utility documentation:
setupWaitPointMock()- exported from@tailor-platform/sdk/test(seepackages/sdk/src/utils/test/index.ts:11)- How to test workflows that use
wait()andresolve()
Example from implementation:
import { setupWaitPointMock } from "@tailor-platform/sdk/test";
const { waitCalls, resolveCalls } = setupWaitPointMock({
onWait: (key, payload) => {
return { approved: true };
}
});Suggested location: Add a new subsection under "Workflow Tests" titled "Testing Wait Points"
Recommended Actions
-
Update
packages/sdk/docs/services/workflow.md:- Add comprehensive section on wait/resolve feature with examples
- Document type constraints (payload must be JSON-compatible, result can be Jsonifiable)
- Explain local vs production behavior
-
Update
CLAUDE.md:- Add wait point rules to the "Workflows" gotchas section
- Mention argument reordering during bundling
-
Update
packages/sdk/docs/testing.md:- Add section on testing workflows with wait points
- Document
setupWaitPointMock()utility with example usage
-
Reference the example:
- Consider adding a reference to
example/workflows/approval.tsin the workflow docs as a working example
- Consider adding a reference to
Code Metrics Report (packages/sdk)
Details | | main (b5d120d) | #985 (6d3b825) | +/- |
|--------------------|----------------|----------------|-------|
+ | Coverage | 57.8% | 57.9% | +0.0% |
| Files | 343 | 344 | +1 |
| Lines | 11475 | 11587 | +112 |
+ | Covered | 6639 | 6715 | +76 |
+ | Code to Test Ratio | 1:0.4 | 1:0.4 | +0.0 |
| Code | 71996 | 72688 | +692 |
+ | Test | 29425 | 29753 | +328 |Code coverage of files in pull request scope (85.6% → 82.3%)
SDK Configure Bundle Size
Runtime Performance
Type Performance (instantiations)
Reported by octocov |
| hasAwait: false, | ||
| }); | ||
| } | ||
| return; // Skip further processing for this node |
There was a problem hiding this comment.
🔴 Early return in detectExtendedTriggerCalls prevents walking children of resolve call nodes, missing nested trigger/resolve calls inside callbacks
In detectExtendedTriggerCalls, after detecting a .resolve() call on a known job, the return at line 164 exits the walk function entirely, skipping the child-walking loop at lines 225-232. This means any .trigger() or .resolve() calls nested inside the resolve callback argument won't be detected or transformed.
Affected code paths and example
This function is used by transformFunctionTriggers which is the sole transform for resolver, executor, and auth hook bundlers (via createTriggerTransformPlugin at trigger-context.ts:143). For example, a resolver with:
await processOrder.resolve("approval", executionId, async (payload) => {
const data = await fetchData.trigger({ id: payload.id });
return { approved: true, data };
});The fetchData.trigger() inside the callback won't be transformed to tailor.workflow.triggerJobFunction(...), producing bundled code that references the non-existent fetchData.trigger at runtime.
By contrast, the analogous detectResolveCalls in source-transformer.ts:36-90 does NOT have this early return — after pushing a known resolve call it falls through to the child-walking loop, correctly detecting nested calls.
Prompt for agents
In detectExtendedTriggerCalls (trigger-transformer.ts around line 164), the `return;` after detecting a .resolve() call prevents the AST walker from visiting child nodes of the call expression. This means nested .trigger() or .resolve() calls inside the callback argument are never detected.
The fix is to remove or restructure the early return so that after pushing a resolve call, the function still falls through to the child-walking loop (lines 225-232). The return was likely intended to prevent the trigger detection block (lines 167-221) from running on the same node, but that block already checks for propertyName === 'trigger' so it won't match a resolve call. Simply removing the `return;` at line 164 should be sufficient.
For reference, see how source-transformer.ts detectResolveCalls handles this correctly — it pushes the call and continues to walk children without returning early.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Implements SDK support for
tailor.workflow.wait()andtailor.workflow.resolve()(platform-planning#995, function#154).waitPointsconfig tocreateWorkflowJobwithwaitPoint<Payload, Result>()helper for typed wait/resolve definitionswait(key, payload?)function; callers usejob.resolve(key, executionId, callback)to resume waiting executionstailor.workflow.waitinto entry code for jobs with wait points.resolve()calls totailor.workflow.resolve()with argument reordering (key, execId, cb→execId, key, cb)waitandresolvesetupWaitPointMocktest utility for bundled workflow testsUsage