@@ -21,6 +21,14 @@ import type { FlowDefinition, FlowNode, PluginConfig, BranchNode, ConditionNode,
2121
2222interface PluginApi {
2323 registerTool : ( def : object , opts ?: { optional ?: boolean } ) => void ;
24+ registerHook ?: (
25+ name : string ,
26+ handler : ( event : { tool ?: string ; params ?: unknown ; [ k : string ] : unknown } ) =>
27+ | { requireApproval ?: boolean ; prompt ?: string ; block ?: boolean }
28+ | void
29+ | Promise < { requireApproval ?: boolean ; prompt ?: string ; block ?: boolean } | void > ,
30+ opts ?: { priority ?: number } ,
31+ ) => void ;
2432 config ?: {
2533 plugins ?: { entries ?: Record < string , { config ?: PluginConfig } > } ;
2634 gateway ?: { port ?: number ; host ?: string } ;
@@ -66,6 +74,27 @@ function register(api: PluginApi) {
6674 } ) ;
6775 }
6876
77+ // ---- Approval gate for flow_run -----------------------------------------------
78+ // Pause and prompt the user before any flow_run invocation. Flows can have
79+ // side effects (HTTP, exec, agent delegation) so we require explicit consent.
80+ if ( api . registerHook ) {
81+ api . registerHook ( "before_tool_call" , ( event ) => {
82+ if ( event . tool !== "flow_run" ) return ;
83+ const p = ( event . params ?? { } ) as { file ?: string ; flow ?: { flow ?: string } ; version ?: number ; draft ?: boolean } ;
84+ const target = p . file ?? p . flow ?. flow ?? "inline flow" ;
85+ const variant =
86+ p . version != null ? ` v${ p . version } ` : p . draft ? " (draft)" : "" ;
87+ return {
88+ requireApproval : true ,
89+ prompt : `Run clawflow "${ target } "${ variant } ?` ,
90+ } ;
91+ } ) ;
92+ } else {
93+ api . logger ?. warn (
94+ "clawflow: registerHook unavailable — flow_run will run without approval gate. Update OpenClaw to enable." ,
95+ ) ;
96+ }
97+
6998 // ---- Shared helpers ------------------------------------------------------------
7099
71100 /** Resolve a file param to an absolute path using workspace conventions. */
0 commit comments