PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / state / workflows.js

workflows.js in Extendify 3.2.1, at src/Agent/state/workflows.js

134 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { isAbilityWorkflow } from '@agent/lib/abilities';
2 import { AbilityRun } from '@agent/workflows/abilities/components/run';
3 import { abilityWorkflows, workflows } from '@agent/workflows/workflows';
4 import { useQuickEditStore } from '@quick-edit/state/store';
5 import { deepMerge } from '@shared/lib/utils';
6 import { create } from 'zustand';
7 import { devtools, persist } from 'zustand/middleware';
8
9 // Used to check case-insensitive matches for workflow examples
10 const collator = new Intl.Collator(undefined, {
11 sensitivity: 'base',
12 usage: 'search',
13 });
14
15 const state = (set, get) => ({
16 workflow: null,
17 // Data for the tool component that shows up at the end of a workflow
18 whenFinishedToolProps: null,
19 getWorkflow: () => {
20 const curr = get().workflow;
21 // Workflows may define a "parent" workflow via templateId
22 const currId = curr?.templateId || curr?.id;
23 const wf = workflows.find(({ id }) => id === currId);
24 if (!wf?.id) {
25 if (!curr) return null;
26 if (!isAbilityWorkflow(curr.id)) return curr;
27 // The backend never sends whenFinished, so this supplies the gate.
28 const ability = deepMerge(
29 { whenFinished: { component: AbilityRun } },
30 abilityWorkflows[curr.id] ?? {},
31 );
32 return { ...deepMerge(curr, ability), id: curr.id };
33 }
34 return { ...deepMerge(curr, wf || {}), id: curr?.id };
35 },
36 getWorkflowByExample: (example) => {
37 const wf = workflows.find(
38 ({ example: ex }) => collator.compare(ex?.text, example) === 0,
39 );
40 if (!wf?.id) return null;
41 if (!wf.available()) return null;
42 return wf;
43 },
44 // Gets the workflows available to the user
45 // TODO: maybe we need to have a way to include a
46 // workflow regardless of the block being active?
47 getAvailableWorkflows: () => {
48 const wfs = workflows.filter(({ available }) => available());
49 // If a block is set, only include those with 'block'
50 const blockWorkflows = wfs.filter(({ requires }) =>
51 requires?.includes('block'),
52 );
53 if (useQuickEditStore.getState().agentBlock) return blockWorkflows;
54 // otherwise remove all of the above
55 return wfs.filter(({ id }) => !blockWorkflows.some((w) => w.id === id));
56 },
57 getWorkflowsByFeature: ({ requires } = {}) => {
58 if (!requires) return workflows.filter(({ available }) => available());
59 // e.g. requires: ['block']
60 return workflows.filter(
61 ({ available, requires: workflowRequires }) =>
62 available() &&
63 (!requires || workflowRequires?.some((s) => requires.includes(s))),
64 );
65 },
66 workflowData: null,
67 mergeWorkflowData: (data) => {
68 set((state) => {
69 if (!state.workflowData) return { workflowData: data };
70 return {
71 workflowData: { ...state.workflowData, ...data },
72 };
73 });
74 },
75 setWorkflow: (workflow) => {
76 const agentBlockCode = useQuickEditStore.getState().agentBlockCode;
77 const started = workflow
78 ? { ...workflow, startingPage: window.location.href }
79 : null;
80 set({
81 workflow: started,
82 // If a block is selected, add it to the workflow data
83 // previousContent is named this way for legacy reasons
84 workflowData: agentBlockCode ? { previousContent: agentBlockCode } : null,
85 whenFinishedToolProps: null,
86 });
87 if (!started) return;
88 const workflowStarted = get().getWorkflow();
89 window.dispatchEvent(
90 new CustomEvent('extendify-workflow::started', {
91 detail: workflowStarted,
92 }),
93 );
94 try {
95 workflowStarted?.onStarted?.(workflowStarted);
96 } catch {
97 // A workflow that can't read the page still has to start.
98 }
99 },
100 setWhenFinishedToolProps: (whenFinishedToolProps) =>
101 set({ whenFinishedToolProps }),
102 // Declared up front it would hide the workflow while nothing is staged.
103 // Not setWorkflow: that resets workflowData and drops the run's data.
104 requireBlock: () =>
105 set((state) =>
106 state.workflow
107 ? { workflow: { ...state.workflow, requires: ['block'] } }
108 : {},
109 ),
110 });
111
112 export const useWorkflowStore = create()(
113 persist(devtools(state, { name: 'Extendify Agent Workflows' }), {
114 name: `extendify-agent-workflows-${window.extSharedData.siteId}`,
115 merge: (persistedState, currentState) => {
116 const merged = { ...currentState, ...persistedState };
117 // A reload can't carry the staged block a block-patching workflow
118 // depends on, so a rehydrated-open one is always stale. A canvas
119 // session is stale too: its field values never persist.
120 const id = merged.workflow?.templateId || merged.workflow?.id;
121 const registered = workflows.find((wf) => wf.id === id);
122 if (id === 'block-patching' || registered?.whenFinished?.canvas) {
123 return {
124 ...merged,
125 workflow: null,
126 whenFinishedToolProps: null,
127 workflowData: null,
128 };
129 }
130 return merged;
131 },
132 }),
133 );
134