1- import type { PermissionMode } from '@pythoughts/pythinker-code-sdk' ;
1+ import {
2+ savedWorkflowSkillName ,
3+ writeSavedWorkflowSkill ,
4+ type PermissionMode ,
5+ } from '@pythoughts/pythinker-code-sdk' ;
26
7+ import { getDataDir } from '#/utils/paths' ;
38import {
49 DynamicWorkflowStartPermissionPromptComponent ,
510 type DynamicWorkflowStartPermissionChoice ,
@@ -25,6 +30,7 @@ export async function handleDynamicWorkflowCommand(host: SlashCommandHost, args:
2530
2631 const prompt = args . trim ( ) ;
2732 if ( handleModelSubcommand ( host , prompt ) ) return ;
33+ if ( await handleSaveSubcommand ( host , prompt ) ) return ;
2834
2935 const mode = dynamicWorkflowModeSubcommand ( prompt ) ;
3036 if ( mode !== undefined ) {
@@ -114,6 +120,69 @@ function withWorkerModelInstruction(prompt: string, model: string | undefined):
114120 : `${ prompt } \n\nUse model "${ model } " for the DynamicWorkflow subagents in this task.` ;
115121}
116122
123+ /**
124+ * `/workflow save <name>` writes the last run back out as a skill, so a fan-out
125+ * that worked can be re-run by name instead of re-described.
126+ *
127+ * Returns true when the input was a `save` subcommand and has been handled.
128+ */
129+ async function handleSaveSubcommand ( host : SlashCommandHost , input : string ) : Promise < boolean > {
130+ const match = / ^ s a v e (?: \s + ( .* ) ) ? $ / iu. exec ( input ) ;
131+ if ( match === null ) return false ;
132+
133+ const name = match [ 1 ] ?. trim ( ) ?? '' ;
134+ if ( name . length === 0 ) {
135+ host . showError ( 'Usage: /workflow save <name>' ) ;
136+ return true ;
137+ }
138+
139+ const args = host . state . lastDynamicWorkflowArgs ;
140+ if ( args === undefined ) {
141+ host . showError ( 'No Dynamic Workflow has run in this session yet.' ) ;
142+ return true ;
143+ }
144+
145+ const description = stringArg ( args , 'description' ) ;
146+ if ( description === undefined ) {
147+ host . showError ( 'The last Dynamic Workflow has no description to save.' ) ;
148+ return true ;
149+ }
150+
151+ try {
152+ const dir = await writeSavedWorkflowSkill ( {
153+ scope : 'project' ,
154+ projectRoot : host . state . appState . workDir ,
155+ brandHomeDir : getDataDir ( ) ,
156+ workflow : {
157+ name,
158+ description,
159+ subagentType : stringArg ( args , 'subagent_type' ) ,
160+ promptTemplate : stringArg ( args , 'prompt_template' ) ,
161+ model : stringArg ( args , 'model' ) ,
162+ effort : stringArg ( args , 'effort' ) ,
163+ outputSchema : recordArg ( args , 'output_schema' ) ,
164+ } ,
165+ } ) ;
166+ host . refreshSlashCommandAutocomplete ( ) ;
167+ host . showStatus ( `Saved /${ savedWorkflowSkillName ( name ) } to ${ dir } .` ) ;
168+ } catch ( error ) {
169+ host . showError ( `Failed to save workflow: ${ formatErrorMessage ( error ) } ` ) ;
170+ }
171+ return true ;
172+ }
173+
174+ function stringArg ( args : Record < string , unknown > , key : string ) : string | undefined {
175+ const value = args [ key ] ;
176+ return typeof value === 'string' && value . trim ( ) . length > 0 ? value : undefined ;
177+ }
178+
179+ function recordArg ( args : Record < string , unknown > , key : string ) : Record < string , unknown > | undefined {
180+ const value = args [ key ] ;
181+ return typeof value === 'object' && value !== null && ! Array . isArray ( value )
182+ ? ( value as Record < string , unknown > )
183+ : undefined ;
184+ }
185+
117186/** Returns true when the input was a `model` subcommand and has been handled. */
118187function handleModelSubcommand ( host : SlashCommandHost , input : string ) : boolean {
119188 const match = / ^ m o d e l (?: \s + ( .* ) ) ? $ / iu. exec ( input ) ;
0 commit comments