@@ -91,7 +91,7 @@ export class WorkflowAgent extends Agent<Bindings, WorkflowAgentState> {
9191 private static readonly PERSIST_DEBOUNCE_MS = 500 ;
9292 private static readonly STORAGE_KEY_DIRTY = "dirty:persist" ;
9393 private static readonly STORAGE_PREFIX_EXEC_BUFFER = "execbuf:" ;
94- private static readonly STORAGE_PREFIX_HITL = "hitl :" ;
94+ private static readonly STORAGE_PREFIX_FORM = "form :" ;
9595
9696 initialState : WorkflowAgentState = { } ;
9797
@@ -512,6 +512,9 @@ export class WorkflowAgent extends Agent<Bindings, WorkflowAgentState> {
512512 private async routeExecutionUpdate (
513513 execution : WorkflowExecution
514514 ) : Promise < void > {
515+ // Extract and store form schemas from node outputs
516+ await this . extractFormSchemas ( execution ) ;
517+
515518 const conn = this . findConnectionByExecutionId ( execution . id ) ;
516519 if ( conn ) {
517520 this . sendExecutionUpdate ( conn , execution ) ;
@@ -525,6 +528,34 @@ export class WorkflowAgent extends Agent<Bindings, WorkflowAgentState> {
525528 ) ;
526529 }
527530
531+ /**
532+ * Scan node outputs for form schema data (`schema` + `token`) and
533+ * store them in DO transactional storage. This is how form nodes
534+ * register their field definitions without touching the main DB.
535+ */
536+ private async extractFormSchemas (
537+ execution : WorkflowExecution
538+ ) : Promise < void > {
539+ for ( const nodeExec of execution . nodeExecutions ) {
540+ if (
541+ nodeExec . status === "completed" &&
542+ nodeExec . outputs &&
543+ typeof nodeExec . outputs . schema === "string" &&
544+ typeof nodeExec . outputs . token === "string"
545+ ) {
546+ const key =
547+ WorkflowAgent . STORAGE_PREFIX_FORM +
548+ nodeExec . outputs . token +
549+ ":schema" ;
550+ // Only store if not already present (idempotent)
551+ const existing = await this . storage . get ( key ) ;
552+ if ( ! existing ) {
553+ await this . storage . put ( key , nodeExec . outputs . schema ) ;
554+ }
555+ }
556+ }
557+ }
558+
528559 /**
529560 * Scan live connections for one subscribed to the given execution.
530561 * Connection state (`connection.setState`) survives DO hibernation via
@@ -570,29 +601,35 @@ export class WorkflowAgent extends Agent<Bindings, WorkflowAgentState> {
570601 }
571602 }
572603
573- // ── HITL form state ───────────────────────────────────────────────────
604+ // ── Form state ───── ───────────────────────────────────────────────────
574605
575606 /**
576- * Check if a HITL form has already been submitted.
607+ * Check if a form has already been submitted.
577608 * Returns `{ submitted: boolean }`.
578609 */
579- async getHitlFormStatus ( token : string ) : Promise < { submitted : boolean } > {
580- const key = WorkflowAgent . STORAGE_PREFIX_HITL + token ;
610+ async getFormStatus (
611+ token : string
612+ ) : Promise < { submitted : boolean ; schema ?: string } > {
613+ const key = WorkflowAgent . STORAGE_PREFIX_FORM + token ;
581614 const record = await this . storage . get < { submitted : boolean } > ( key ) ;
582- return { submitted : record ?. submitted ?? false } ;
615+ const schema = await this . storage . get < string > ( key + ":schema" ) ;
616+ return {
617+ submitted : record ?. submitted ?? false ,
618+ ...( schema ? { schema } : { } ) ,
619+ } ;
583620 }
584621
585622 /**
586- * Atomically check-and-submit a HITL form response.
623+ * Atomically check-and-submit a form response.
587624 * Rejects duplicate submissions. On success, sends the event to the
588625 * EXECUTE workflow instance to resume the paused node.
589626 */
590- async checkAndSubmitHitlForm (
627+ async checkAndSubmitForm (
591628 token : string ,
592629 executionId : string ,
593- response : { text ?: string ; approved ?: boolean ; metadata ?: Record < string , unknown > }
630+ response : Record < string , unknown >
594631 ) : Promise < { success : boolean ; error ?: string } > {
595- const key = WorkflowAgent . STORAGE_PREFIX_HITL + token ;
632+ const key = WorkflowAgent . STORAGE_PREFIX_FORM + token ;
596633 const existing = await this . storage . get < { submitted : boolean } > ( key ) ;
597634
598635 if ( existing ?. submitted ) {
@@ -606,19 +643,15 @@ export class WorkflowAgent extends Agent<Bindings, WorkflowAgentState> {
606643 try {
607644 const instance = await this . env . EXECUTE . get ( executionId ) ;
608645 await instance . sendEvent ( {
609- type : `hitl -response-${ token } ` ,
646+ type : `form -response-${ token } ` ,
610647 payload : {
611- outputs : {
612- response : response . text ?? "" ,
613- approved : response . approved ?? false ,
614- metadata : response . metadata ?? { } ,
615- } ,
648+ outputs : { response } ,
616649 usage : 0 ,
617650 } ,
618651 } ) ;
619652 return { success : true } ;
620653 } catch ( error ) {
621- console . error ( "Failed to send HITL event:" , error ) ;
654+ console . error ( "Failed to send form event:" , error ) ;
622655 return {
623656 success : false ,
624657 error : "Failed to resume workflow. The execution may have expired." ,
0 commit comments